001 /*
002 * Copyright (c) 2009 The JOMC Project
003 * Copyright (c) 2005 Christian Schulte <cs@jomc.org>
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions
008 * are met:
009 *
010 * o Redistributions of source code must retain the above copyright
011 * notice, this list of conditions and the following disclaimer.
012 *
013 * o Redistributions in binary form must reproduce the above copyright
014 * notice, this list of conditions and the following disclaimer in
015 * the documentation and/or other materials provided with the
016 * distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 *
030 * $Id: JavaClasses.java 992 2009-11-19 00:11:06Z schulte2005 $
031 *
032 */
033 package org.jomc.tools;
034
035 import java.io.ByteArrayInputStream;
036 import java.io.ByteArrayOutputStream;
037 import java.io.File;
038 import java.io.IOException;
039 import java.io.InputStream;
040 import java.net.URL;
041 import java.text.MessageFormat;
042 import java.util.List;
043 import java.util.ResourceBundle;
044 import java.util.logging.Level;
045 import java.util.zip.GZIPInputStream;
046 import java.util.zip.GZIPOutputStream;
047 import javax.xml.bind.JAXBElement;
048 import javax.xml.bind.JAXBException;
049 import javax.xml.bind.Marshaller;
050 import javax.xml.bind.Unmarshaller;
051 import javax.xml.bind.util.JAXBResult;
052 import javax.xml.bind.util.JAXBSource;
053 import javax.xml.transform.Transformer;
054 import javax.xml.transform.TransformerException;
055 import org.apache.bcel.classfile.Attribute;
056 import org.apache.bcel.classfile.ClassParser;
057 import org.apache.bcel.classfile.Constant;
058 import org.apache.bcel.classfile.ConstantPool;
059 import org.apache.bcel.classfile.ConstantUtf8;
060 import org.apache.bcel.classfile.JavaClass;
061 import org.apache.bcel.classfile.Unknown;
062 import org.jomc.model.Dependencies;
063 import org.jomc.model.Dependency;
064 import org.jomc.model.Implementation;
065 import org.jomc.model.Message;
066 import org.jomc.model.Messages;
067 import org.jomc.model.ModelObject;
068 import org.jomc.model.ModelObjectValidationReport;
069 import org.jomc.model.Module;
070 import org.jomc.model.ObjectFactory;
071 import org.jomc.model.Properties;
072 import org.jomc.model.Property;
073 import org.jomc.model.Specification;
074 import org.jomc.model.SpecificationReference;
075 import org.jomc.model.Specifications;
076 import org.jomc.util.ParseException;
077 import org.jomc.util.TokenMgrError;
078 import org.jomc.util.VersionParser;
079
080 /**
081 * Manages Java classes.
082 *
083 * <p><b>Use cases</b><br/><ul>
084 * <li>{@link #commitClasses(javax.xml.bind.Marshaller, java.io.File) }</li>
085 * <li>{@link #commitClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, java.io.File) }</li>
086 * <li>{@link #commitClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, java.io.File) }</li>
087 * <li>{@link #commitClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, java.io.File) }</li>
088 * <li>{@link #validateClasses(javax.xml.bind.Unmarshaller, java.io.File) }</li>
089 * <li>{@link #validateClasses(javax.xml.bind.Unmarshaller, java.lang.ClassLoader) }</li>
090 * <li>{@link #validateClasses(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.io.File) }</li>
091 * <li>{@link #validateClasses(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.lang.ClassLoader) }</li>
092 * <li>{@link #validateClasses(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass) }</li>
093 * <li>{@link #validateClasses(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass) }</li>
094 * <li>{@link #transformClasses(javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List) }</li>
095 * <li>{@link #transformClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List) }</li>
096 * <li>{@link #transformClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List) }</li>
097 * <li>{@link #transformClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List) }</li>
098 * </ul></p>
099 *
100 * @author <a href="mailto:cs@jomc.org">Christian Schulte</a>
101 * @version $Id: JavaClasses.java 992 2009-11-19 00:11:06Z schulte2005 $
102 *
103 * @see #getModules()
104 * @see org.jomc.model.ModelManager#getContext(java.lang.ClassLoader)
105 * @see org.jomc.model.ModelManager#getMarshaller(java.lang.ClassLoader)
106 * @see org.jomc.model.ModelManager#getUnmarshaller(java.lang.ClassLoader)
107 * @see ModelObjectValidationReport#isModelObjectValid()
108 */
109 public class JavaClasses extends JomcTool
110 {
111
112 /** Creates a new {@code JavaClasses} instance. */
113 public JavaClasses()
114 {
115 super();
116 }
117
118 /**
119 * Creates a new {@code JavaClasses} instance taking a {@code JavaClasses} instance to initialize the instance with.
120 *
121 * @param tool The instance to initialize the new instance with,
122 */
123 public JavaClasses( final JavaClasses tool )
124 {
125 super( tool );
126 }
127
128 /**
129 * Commits meta-data of the modules of the instance to compiled Java classes.
130 *
131 * @param marshaller The marshaller to use for committing the classes.
132 * @param classesDirectory The directory holding the compiled class files.
133 *
134 * @throws NullPointerException if {@code marshaller} or {@code classesDirectory} is {@code null}.
135 * @throws IOException if committing meta-data fails.
136 *
137 * @see #commitClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, java.io.File)
138 */
139 public void commitClasses( final Marshaller marshaller, final File classesDirectory ) throws IOException
140 {
141 if ( marshaller == null )
142 {
143 throw new NullPointerException( "marshaller" );
144 }
145 if ( classesDirectory == null )
146 {
147 throw new NullPointerException( "classesDirectory" );
148 }
149
150 for ( Module m : this.getModules().getModule() )
151 {
152 this.commitClasses( m, marshaller, classesDirectory );
153 }
154 }
155
156 /**
157 * Commits meta-data of a given module of the modules of the instance to compiled Java classes.
158 *
159 * @param module The module to process.
160 * @param marshaller The marshaller to use for committing the classes.
161 * @param classesDirectory The directory holding the compiled class files.
162 *
163 * @throws NullPointerException if {@code module}, {@code marshaller} or {@code classesDirectory} is {@code null}.
164 * @throws IOException if committing meta-data fails.
165 *
166 * @see #commitClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, java.io.File)
167 * @see #commitClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, java.io.File)
168 */
169 public void commitClasses( final Module module, final Marshaller marshaller, final File classesDirectory )
170 throws IOException
171 {
172 if ( module == null )
173 {
174 throw new NullPointerException( "module" );
175 }
176 if ( marshaller == null )
177 {
178 throw new NullPointerException( "marshaller" );
179 }
180 if ( classesDirectory == null )
181 {
182 throw new NullPointerException( "classesDirectory" );
183 }
184
185 if ( module.getSpecifications() != null )
186 {
187 for ( Specification s : module.getSpecifications().getSpecification() )
188 {
189 this.commitClasses( s, marshaller, classesDirectory );
190 }
191 }
192 if ( module.getImplementations() != null )
193 {
194 for ( Implementation i : module.getImplementations().getImplementation() )
195 {
196 this.commitClasses( i, marshaller, classesDirectory );
197 }
198 }
199 }
200
201 /**
202 * Commits meta-data of a given specification of the modules of the instance to compiled Java classes.
203 *
204 * @param specification The specification to process.
205 * @param marshaller The marshaller to use for committing the classes.
206 * @param classesDirectory The directory holding the compiled class files.
207 *
208 * @throws NullPointerException if {@code specification}, {@code marshaller} or {@code classesDirectory} is
209 * {@code null}.
210 * @throws IOException if committing meta-data fails.
211 */
212 public void commitClasses( final Specification specification, final Marshaller marshaller,
213 final File classesDirectory ) throws IOException
214 {
215 if ( specification == null )
216 {
217 throw new NullPointerException( "specification" );
218 }
219 if ( marshaller == null )
220 {
221 throw new NullPointerException( "marshaller" );
222 }
223 if ( classesDirectory == null )
224 {
225 throw new NullPointerException( "classesDirectory" );
226 }
227
228 if ( this.isJavaClassDeclaration( specification ) )
229 {
230 final String classLocation = specification.getClazz().replace( '.', File.separatorChar ) + ".class";
231 final File classFile = new File( classesDirectory, classLocation );
232 if ( this.isLoggable( Level.INFO ) )
233 {
234 this.log( Level.INFO, this.getMessage( "committing", new Object[]
235 {
236 classFile.getAbsolutePath()
237 } ), null );
238
239 }
240
241 final JavaClass javaClass = this.getJavaClass( classFile );
242 this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
243 marshaller, new ObjectFactory().createSpecification( specification ) ) );
244
245 javaClass.dump( classFile );
246 }
247 }
248
249 /**
250 * Commits meta-data of a given implementation of the modules of the instance to compiled Java classes.
251 *
252 * @param implementation The implementation to process.
253 * @param marshaller The marshaller to use for committing the classes.
254 * @param classesDirectory The directory holding the compiled class files.
255 *
256 * @throws NullPointerException if {@code implementation}, {@code marshaller} or {@code classesDirectory} is
257 * {@code null}.
258 * @throws IOException if committing meta-data fails.
259 */
260 public void commitClasses( final Implementation implementation, final Marshaller marshaller,
261 final File classesDirectory ) throws IOException
262 {
263 if ( implementation == null )
264 {
265 throw new NullPointerException( "implementation" );
266 }
267 if ( marshaller == null )
268 {
269 throw new NullPointerException( "marshaller" );
270 }
271 if ( classesDirectory == null )
272 {
273 throw new NullPointerException( "classesDirectory" );
274 }
275
276 if ( this.isJavaClassDeclaration( implementation ) )
277 {
278 Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
279 if ( dependencies == null )
280 {
281 dependencies = new Dependencies();
282 }
283
284 Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
285 if ( properties == null )
286 {
287 properties = new Properties();
288 }
289
290 Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
291 if ( messages == null )
292 {
293 messages = new Messages();
294 }
295
296 Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
297 if ( specifications == null )
298 {
299 specifications = new Specifications();
300 }
301
302 for ( SpecificationReference r : specifications.getReference() )
303 {
304 if ( specifications.getSpecification( r.getIdentifier() ) == null && this.isLoggable( Level.WARNING ) )
305 {
306 this.log( Level.WARNING, this.getMessage( "unresolvedSpecification", new Object[]
307 {
308 r.getIdentifier(), implementation.getIdentifier()
309 } ), null );
310
311 }
312 }
313
314 for ( Dependency d : dependencies.getDependency() )
315 {
316 final Specification s = this.getModules().getSpecification( d.getIdentifier() );
317
318 if ( s != null )
319 {
320 if ( specifications.getSpecification( s.getIdentifier() ) == null )
321 {
322 specifications.getSpecification().add( s );
323 }
324 }
325 else if ( this.isLoggable( Level.WARNING ) )
326 {
327 this.log( Level.WARNING, this.getMessage( "unresolvedDependencySpecification", new Object[]
328 {
329 d.getIdentifier(), d.getName(), implementation.getIdentifier()
330 } ), null );
331
332 }
333 }
334
335 final String classLocation = implementation.getClazz().replace( '.', File.separatorChar ) + ".class";
336 final File classFile = new File( classesDirectory, classLocation );
337
338 if ( this.isLoggable( Level.INFO ) )
339 {
340 this.log( Level.INFO, this.getMessage( "committing", new Object[]
341 {
342 classFile.getAbsolutePath()
343 } ), null );
344
345 }
346
347 final JavaClass javaClass = this.getJavaClass( classFile );
348 final ObjectFactory of = new ObjectFactory();
349
350 this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
351 marshaller, of.createDependencies( dependencies ) ) );
352
353 this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
354 marshaller, of.createProperties( properties ) ) );
355
356 this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
357 marshaller, of.createMessages( messages ) ) );
358
359 this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
360 marshaller, of.createSpecifications( specifications ) ) );
361
362 javaClass.dump( classFile );
363 }
364 }
365
366 /**
367 * Validates compiled Java classes against the modules of the instance.
368 *
369 * @param unmarshaller The unmarshaller to use for validating classes.
370 * @param classesDirectory The directory holding the compiled class files.
371 *
372 * @return The report of the validation.
373 *
374 * @throws NullPointerException if {@code unmarshaller} or {@code classesDirectory} is {@code null}.
375 * @throws IOException if reading class files fails.
376 *
377 * @see #validateClasses(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.io.File)
378 */
379 public ModelObjectValidationReport validateClasses( final Unmarshaller unmarshaller, final File classesDirectory )
380 throws IOException
381 {
382 if ( unmarshaller == null )
383 {
384 throw new NullPointerException( "unmarshaller" );
385 }
386 if ( classesDirectory == null )
387 {
388 throw new NullPointerException( "classesDirectory" );
389 }
390
391 final ModelObjectValidationReport report = new ModelObjectValidationReport(
392 new ObjectFactory().createModules( this.getModules() ) );
393
394 for ( Module m : this.getModules().getModule() )
395 {
396 final ModelObjectValidationReport current = this.validateClasses( m, unmarshaller, classesDirectory );
397 report.getDetails().addAll( current.getDetails() );
398 }
399
400 return report;
401 }
402
403 /**
404 * Validates compiled Java classes against the modules of the instance.
405 *
406 * @param unmarshaller The unmarshaller to use for validating classes.
407 * @param classLoader The class loader to search for classes.
408 *
409 * @return The report of the validation.
410 *
411 * @throws NullPointerException if {@code unmarshaller} or {@code classLoader} is {@code null}.
412 * @throws IOException if reading class files fails.
413 *
414 * @see #validateClasses(org.jomc.model.Module, javax.xml.bind.Unmarshaller, java.lang.ClassLoader)
415 */
416 public ModelObjectValidationReport validateClasses( final Unmarshaller unmarshaller, final ClassLoader classLoader )
417 throws IOException
418 {
419 if ( unmarshaller == null )
420 {
421 throw new NullPointerException( "unmarshaller" );
422 }
423 if ( classLoader == null )
424 {
425 throw new NullPointerException( "classLoader" );
426 }
427
428 final ModelObjectValidationReport report = new ModelObjectValidationReport(
429 new ObjectFactory().createModules( this.getModules() ) );
430
431 for ( Module m : this.getModules().getModule() )
432 {
433 final ModelObjectValidationReport current = this.validateClasses( m, unmarshaller, classLoader );
434 report.getDetails().addAll( current.getDetails() );
435 }
436
437 return report;
438 }
439
440 /**
441 * Validates compiled Java classes against a given module of the modules of the instance.
442 *
443 * @param module The module to process.
444 * @param unmarshaller The unmarshaller to use for validating classes.
445 * @param classesDirectory The directory holding the compiled class files.
446 *
447 * @return The report of the validation.
448 *
449 * @throws NullPointerException if {@code module}, {@code unmarshaller} or {@code classesDirectory} is {@code null}.
450 * @throws IOException if reading class files fails.
451 *
452 * @see #validateClasses(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
453 * @see #validateClasses(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
454 */
455 public ModelObjectValidationReport validateClasses( final Module module, final Unmarshaller unmarshaller,
456 final File classesDirectory ) throws IOException
457 {
458 if ( module == null )
459 {
460 throw new NullPointerException( "module" );
461 }
462 if ( unmarshaller == null )
463 {
464 throw new NullPointerException( "unmarshaller" );
465 }
466 if ( classesDirectory == null )
467 {
468 throw new NullPointerException( "classesDirectory" );
469 }
470
471 final ModelObjectValidationReport report = new ModelObjectValidationReport(
472 new ObjectFactory().createModule( module ) );
473
474 if ( module.getSpecifications() != null )
475 {
476 for ( Specification s : module.getSpecifications().getSpecification() )
477 {
478 if ( this.isJavaClassDeclaration( s ) )
479 {
480 final String classLocation = s.getClazz().replace( '.', File.separatorChar ) + ".class";
481 final File classFile = new File( classesDirectory, classLocation );
482 final ModelObjectValidationReport current =
483 this.validateClasses( s, unmarshaller, this.getJavaClass( classFile ) );
484
485 report.getDetails().addAll( current.getDetails() );
486 }
487 }
488 }
489
490 if ( module.getImplementations() != null )
491 {
492 for ( Implementation i : module.getImplementations().getImplementation() )
493 {
494 if ( this.isJavaClassDeclaration( i ) )
495 {
496 final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
497 final File classFile = new File( classesDirectory, classLocation );
498 final JavaClass javaClass = this.getJavaClass( classFile );
499 final ModelObjectValidationReport current =
500 this.validateClasses( i, unmarshaller, javaClass );
501
502 report.getDetails().addAll( current.getDetails() );
503 }
504 }
505 }
506
507 return report;
508 }
509
510 /**
511 * Validates compiled Java classes against a given module of the modules of the instance.
512 *
513 * @param module The module to process.
514 * @param unmarshaller The unmarshaller to use for validating classes.
515 * @param classLoader The class loader to search for classes.
516 *
517 * @return The report of the validation.
518 *
519 * @throws NullPointerException if {@code module}, {@code unmarshaller} or {@code classLoader} is {@code null}.
520 * @throws IOException if reading class files fails.
521 *
522 * @see #validateClasses(org.jomc.model.Specification, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
523 * @see #validateClasses(org.jomc.model.Implementation, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass)
524 */
525 public ModelObjectValidationReport validateClasses( final Module module, final Unmarshaller unmarshaller,
526 final ClassLoader classLoader ) throws IOException
527 {
528 if ( module == null )
529 {
530 throw new NullPointerException( "module" );
531 }
532 if ( unmarshaller == null )
533 {
534 throw new NullPointerException( "unmarshaller" );
535 }
536 if ( classLoader == null )
537 {
538 throw new NullPointerException( "classLoader" );
539 }
540
541 final ModelObjectValidationReport report = new ModelObjectValidationReport(
542 new ObjectFactory().createModule( module ) );
543
544 if ( module.getSpecifications() != null )
545 {
546 for ( Specification s : module.getSpecifications().getSpecification() )
547 {
548 if ( this.isJavaClassDeclaration( s ) )
549 {
550 final String classLocation = s.getClazz().replace( '.', '/' ) + ".class";
551 final URL classUrl = classLoader.getResource( classLocation );
552
553 if ( classUrl == null )
554 {
555 throw new IOException( this.getMessage( "resourceNotFound", new Object[]
556 {
557 classLocation
558 } ) );
559
560 }
561
562 final JavaClass javaClass = this.getJavaClass( classUrl, classLocation );
563 final ModelObjectValidationReport current =
564 this.validateClasses( s, unmarshaller, javaClass );
565
566 report.getDetails().addAll( current.getDetails() );
567 }
568 }
569 }
570
571 if ( module.getImplementations() != null )
572 {
573 for ( Implementation i : module.getImplementations().getImplementation() )
574 {
575 if ( this.isJavaClassDeclaration( i ) )
576 {
577 final String classLocation = i.getClazz().replace( '.', '/' ) + ".class";
578 final URL classUrl = classLoader.getResource( classLocation );
579
580 if ( classUrl == null )
581 {
582 throw new IOException( this.getMessage( "resourceNotFound", new Object[]
583 {
584 classLocation
585 } ) );
586
587 }
588
589 final JavaClass javaClass = this.getJavaClass( classUrl, classLocation );
590 final ModelObjectValidationReport current =
591 this.validateClasses( i, unmarshaller, javaClass );
592
593 report.getDetails().addAll( current.getDetails() );
594 }
595 }
596 }
597
598 return report;
599 }
600
601 /**
602 * Validates compiled Java classes against a given specification of the modules of the instance.
603 *
604 * @param specification The specification to process.
605 * @param unmarshaller The unmarshaller to use for validating classes.
606 * @param javaClass The class to validate.
607 *
608 * @return The report of the validation.
609 *
610 * @throws NullPointerException if {@code specification}, {@code unmarshaller} or {@code javaClass} is {@code null}.
611 * @throws IOException if reading class files fails.
612 */
613 public ModelObjectValidationReport validateClasses( final Specification specification,
614 final Unmarshaller unmarshaller, final JavaClass javaClass )
615 throws IOException
616 {
617 if ( specification == null )
618 {
619 throw new NullPointerException( "specification" );
620 }
621 if ( unmarshaller == null )
622 {
623 throw new NullPointerException( "unmarshaller" );
624 }
625 if ( javaClass == null )
626 {
627 throw new NullPointerException( "javaClass" );
628 }
629
630 if ( this.isLoggable( Level.INFO ) )
631 {
632 this.log( Level.INFO, this.getMessage( "validatingSpecification", new Object[]
633 {
634 specification.getIdentifier()
635 } ), null );
636
637 }
638
639 final ModelObjectValidationReport report = new ModelObjectValidationReport(
640 new ObjectFactory().createSpecification( specification ) );
641
642 Specification decoded = null;
643 final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
644 if ( bytes != null )
645 {
646 decoded = this.decodeModelObject( unmarshaller, bytes, Specification.class );
647 }
648
649 if ( decoded != null )
650 {
651 if ( decoded.getMultiplicity() != specification.getMultiplicity() )
652 {
653 report.getDetails().add( new ModelObjectValidationReport.Detail(
654 "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE,
655 this.getMessage( "illegalMultiplicity", new Object[]
656 {
657 specification.getIdentifier(), specification.getMultiplicity().value(),
658 decoded.getMultiplicity().value()
659 } ) ) );
660
661 }
662
663 if ( decoded.getScope() == null
664 ? specification.getScope() != null
665 : !decoded.getScope().equals( specification.getScope() ) )
666 {
667 report.getDetails().add( new ModelObjectValidationReport.Detail(
668 "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE,
669 this.getMessage( "illegalScope", new Object[]
670 {
671 specification.getIdentifier(),
672 specification.getScope() == null ? "Multiton" : specification.getScope(),
673 decoded.getScope() == null ? "Multiton" : decoded.getScope()
674 } ) ) );
675
676 }
677
678 if ( decoded.getClazz() == null
679 ? specification.getClazz() != null
680 : !decoded.getClazz().equals( specification.getClazz() ) )
681 {
682 report.getDetails().add( new ModelObjectValidationReport.Detail(
683 "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
684 this.getMessage( "illegalSpecificationClass", new Object[]
685 {
686 decoded.getIdentifier(), specification.getClazz(), decoded.getClazz()
687 } ) ) );
688
689 }
690 }
691 else if ( this.isLoggable( Level.WARNING ) )
692 {
693 this.log( Level.WARNING, this.getMessage( "cannotValidateSpecification", new Object[]
694 {
695 specification.getIdentifier(), Specification.class.getName()
696 } ), null );
697
698 }
699
700 return report;
701 }
702
703 /**
704 * Validates compiled Java classes against a given implementation of the modules of the instance.
705 *
706 * @param implementation The implementation to process.
707 * @param unmarshaller The unmarshaller to use for validating classes.
708 * @param javaClass The class to validate.
709 *
710 * @return The report of the validation.
711 *
712 * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code javaClass} is
713 * {@code null}.
714 * @throws IOException if reading class files fails.
715 */
716 public ModelObjectValidationReport validateClasses( final Implementation implementation,
717 final Unmarshaller unmarshaller, final JavaClass javaClass )
718 throws IOException
719 {
720 if ( implementation == null )
721 {
722 throw new NullPointerException( "implementation" );
723 }
724 if ( unmarshaller == null )
725 {
726 throw new NullPointerException( "unmarshaller" );
727 }
728 if ( javaClass == null )
729 {
730 throw new NullPointerException( "javaClass" );
731 }
732
733 if ( this.isLoggable( Level.INFO ) )
734 {
735 this.log( Level.INFO, this.getMessage( "validatingImplementation", new Object[]
736 {
737 implementation.getIdentifier()
738 } ), null );
739
740 }
741
742 final ModelObjectValidationReport report = new ModelObjectValidationReport(
743 new ObjectFactory().createImplementation( implementation ) );
744
745 try
746 {
747 Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
748 if ( dependencies == null )
749 {
750 dependencies = new Dependencies();
751 }
752
753 Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
754 if ( properties == null )
755 {
756 properties = new Properties();
757 }
758
759 Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
760 if ( messages == null )
761 {
762 messages = new Messages();
763 }
764
765 Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
766 if ( specifications == null )
767 {
768 specifications = new Specifications();
769 }
770
771 Dependencies decodedDependencies = null;
772 byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
773 if ( bytes != null )
774 {
775 decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
776 }
777
778 Properties decodedProperties = null;
779 bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
780 if ( bytes != null )
781 {
782 decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
783 }
784
785 Messages decodedMessages = null;
786 bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
787 if ( bytes != null )
788 {
789 decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
790 }
791
792 Specifications decodedSpecifications = null;
793 bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
794 if ( bytes != null )
795 {
796 decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
797 }
798
799 if ( decodedDependencies != null )
800 {
801 for ( Dependency decodedDependency : decodedDependencies.getDependency() )
802 {
803 final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
804
805 if ( dependency == null )
806 {
807 report.getDetails().add( new ModelObjectValidationReport.Detail(
808 "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
809 this.getMessage( "missingDependency", new Object[]
810 {
811 implementation.getIdentifier(), decodedDependency.getName()
812 } ) ) );
813
814 }
815
816 final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );
817
818 if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null &&
819 VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
820 {
821 final Module moduleOfSpecification =
822 this.getModules().getModuleOfSpecification( s.getIdentifier() );
823
824 final Module moduleOfImplementation =
825 this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
826
827 report.getDetails().add( new ModelObjectValidationReport.Detail(
828 "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
829 this.getMessage( "incompatibleDependency", new Object[]
830 {
831 javaClass.getClassName(), moduleOfImplementation == null
832 ? "<>" : moduleOfImplementation.getName(),
833 s.getIdentifier(), moduleOfSpecification == null
834 ? "<>" : moduleOfSpecification.getName(),
835 decodedDependency.getVersion(), s.getVersion()
836 } ) ) );
837
838 }
839 }
840 }
841 else if ( this.isLoggable( Level.WARNING ) )
842 {
843 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
844 {
845 implementation.getIdentifier(), Dependencies.class.getName()
846 } ), null );
847
848 }
849
850 if ( decodedProperties != null )
851 {
852 for ( Property decodedProperty : decodedProperties.getProperty() )
853 {
854 final Property property = properties.getProperty( decodedProperty.getName() );
855
856 if ( property == null )
857 {
858 report.getDetails().add( new ModelObjectValidationReport.Detail(
859 "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE,
860 this.getMessage( "missingProperty", new Object[]
861 {
862 implementation.getIdentifier(), decodedProperty.getName()
863 } ) ) );
864
865 }
866 else
867 {
868 if ( decodedProperty.getType() == null
869 ? property.getType() != null
870 : !decodedProperty.getType().equals( property.getType() ) )
871 {
872 report.getDetails().add( new ModelObjectValidationReport.Detail(
873 "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE,
874 this.getMessage( "illegalPropertyType", new Object[]
875 {
876 implementation.getIdentifier(), decodedProperty.getName(),
877 property.getType() == null ? "default" : property.getType(),
878 decodedProperty.getType() == null ? "default" : decodedProperty.getType()
879 } ) ) );
880
881 }
882 }
883 }
884 }
885 else if ( this.isLoggable( Level.WARNING ) )
886 {
887 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
888 {
889 implementation.getIdentifier(), Properties.class.getName()
890 } ), null );
891
892 }
893
894 if ( decodedMessages != null )
895 {
896 for ( Message decodedMessage : decodedMessages.getMessage() )
897 {
898 final Message message = messages.getMessage( decodedMessage.getName() );
899
900 if ( message == null )
901 {
902 report.getDetails().add( new ModelObjectValidationReport.Detail(
903 "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE,
904 this.getMessage( "missingMessage", new Object[]
905 {
906 implementation.getIdentifier(), decodedMessage.getName()
907 } ) ) );
908
909 }
910 }
911 }
912 else if ( this.isLoggable( Level.WARNING ) )
913 {
914 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
915 {
916 implementation.getIdentifier(), Messages.class.getName()
917 } ), null );
918
919 }
920
921 if ( decodedSpecifications != null )
922 {
923 for ( Specification decodedSpecification : decodedSpecifications.getSpecification() )
924 {
925 final Specification specification =
926 this.getModules().getSpecification( decodedSpecification.getIdentifier() );
927
928 if ( specification == null )
929 {
930 report.getDetails().add( new ModelObjectValidationReport.Detail(
931 "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
932 this.getMessage( "missingSpecification", new Object[]
933 {
934 implementation.getIdentifier(), decodedSpecification.getIdentifier()
935 } ) ) );
936
937 }
938 else
939 {
940 if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
941 {
942 report.getDetails().add( new ModelObjectValidationReport.Detail(
943 "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE,
944 this.getMessage( "illegalMultiplicity", new Object[]
945 {
946 specification.getIdentifier(), specification.getMultiplicity().value(),
947 decodedSpecification.getMultiplicity().value()
948 } ) ) );
949
950 }
951
952 if ( decodedSpecification.getScope() == null
953 ? specification.getScope() != null
954 : !decodedSpecification.getScope().equals( specification.getScope() ) )
955 {
956 report.getDetails().add( new ModelObjectValidationReport.Detail(
957 "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE,
958 this.getMessage( "illegalScope", new Object[]
959 {
960 decodedSpecification.getIdentifier(),
961 specification.getScope() == null
962 ? "Multiton" : specification.getScope(),
963 decodedSpecification.getScope() == null
964 ? "Multiton" : decodedSpecification.getScope()
965 } ) ) );
966
967 }
968
969 if ( decodedSpecification.getClazz() == null
970 ? specification.getClazz() != null
971 : !decodedSpecification.getClazz().equals( specification.getClazz() ) )
972 {
973 report.getDetails().add( new ModelObjectValidationReport.Detail(
974 "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
975 this.getMessage( "illegalSpecificationClass", new Object[]
976 {
977 decodedSpecification.getIdentifier(), specification.getClazz(),
978 decodedSpecification.getClazz()
979 } ) ) );
980
981 }
982 }
983 }
984
985 for ( SpecificationReference decodedReference : decodedSpecifications.getReference() )
986 {
987 final Specification specification =
988 specifications.getSpecification( decodedReference.getIdentifier() );
989
990 if ( specification == null )
991 {
992 report.getDetails().add( new ModelObjectValidationReport.Detail(
993 "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
994 this.getMessage( "missingSpecification", new Object[]
995 {
996 implementation.getIdentifier(), decodedReference.getIdentifier()
997 } ) ) );
998
999 }
1000 else if ( decodedReference.getVersion() != null && specification.getVersion() != null &&
1001 VersionParser.compare( decodedReference.getVersion(), specification.getVersion() ) != 0 )
1002 {
1003 final Module moduleOfSpecification =
1004 this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );
1005
1006 final Module moduleOfImplementation =
1007 this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
1008
1009 report.getDetails().add( new ModelObjectValidationReport.Detail(
1010 "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE,
1011 this.getMessage( "incompatibleImplementation", new Object[]
1012 {
1013 javaClass.getClassName(), moduleOfImplementation == null
1014 ? "<>" : moduleOfImplementation.getName(),
1015 specification.getIdentifier(), moduleOfSpecification == null
1016 ? "<>" : moduleOfSpecification.getName(),
1017 decodedReference.getVersion(), specification.getVersion()
1018 } ) ) );
1019
1020 }
1021 }
1022 }
1023 else if ( this.isLoggable( Level.WARNING ) )
1024 {
1025 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
1026 {
1027 implementation.getIdentifier(), Specifications.class.getName()
1028 } ), null );
1029
1030 }
1031
1032 return report;
1033 }
1034 catch ( final ParseException e )
1035 {
1036 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1037 }
1038 catch ( final TokenMgrError e )
1039 {
1040 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1041 }
1042 }
1043
1044 /**
1045 * Transforms committed meta-data of compiled Java classes of the modules of the instance.
1046 *
1047 * @param marshaller The marshaller to use for transforming classes.
1048 * @param unmarshaller The unmarshaller to use for transforming classes.
1049 * @param classesDirectory The directory holding the compiled class files.
1050 * @param transformers The transformers to use for transforming the classes.
1051 *
1052 * @throws NullPointerException if {@code marshaller}, {@code unmarshaller}, {@code classesDirectory} or
1053 * {@code transformers} is {@code null}.
1054 * @throws IOException if accessing class files fails.
1055 * @throws TransformerException if transforming class files fails.
1056 *
1057 * @see #transformClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List)
1058 */
1059 public void transformClasses( final Marshaller marshaller, final Unmarshaller unmarshaller,
1060 final File classesDirectory, final List<Transformer> transformers )
1061 throws IOException, TransformerException
1062 {
1063 if ( marshaller == null )
1064 {
1065 throw new NullPointerException( "marshaller" );
1066 }
1067 if ( unmarshaller == null )
1068 {
1069 throw new NullPointerException( "unmarshaller" );
1070 }
1071 if ( transformers == null )
1072 {
1073 throw new NullPointerException( "transformers" );
1074 }
1075 if ( classesDirectory == null )
1076 {
1077 throw new NullPointerException( "classesDirectory" );
1078 }
1079
1080 for ( Module m : this.getModules().getModule() )
1081 {
1082 this.transformClasses( m, marshaller, unmarshaller, classesDirectory, transformers );
1083 }
1084 }
1085
1086 /**
1087 * Transforms committed meta-data of compiled Java classes of a given module of the modules of the instance.
1088 *
1089 * @param module The module to process.
1090 * @param marshaller The marshaller to use for transforming classes.
1091 * @param unmarshaller The unmarshaller to use for transforming classes.
1092 * @param classesDirectory The directory holding the compiled class files.
1093 * @param transformers The transformers to use for transforming the classes.
1094 *
1095 * @throws NullPointerException if {@code module}, {@code marshaller}, {@code unmarshaller},
1096 * {@code classesDirectory} or {@code transformers} is {@code null}.
1097 * @throws IOException if accessing class files fails.
1098 * @throws TransformerException if transforming class files fails.
1099 *
1100 * @see #transformClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1101 * @see #transformClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1102 */
1103 public void transformClasses( final Module module, final Marshaller marshaller, final Unmarshaller unmarshaller,
1104 final File classesDirectory, final List<Transformer> transformers )
1105 throws IOException, TransformerException
1106 {
1107 if ( module == null )
1108 {
1109 throw new NullPointerException( "module" );
1110 }
1111 if ( marshaller == null )
1112 {
1113 throw new NullPointerException( "marshaller" );
1114 }
1115 if ( unmarshaller == null )
1116 {
1117 throw new NullPointerException( "unmarshaller" );
1118 }
1119 if ( transformers == null )
1120 {
1121 throw new NullPointerException( "transformers" );
1122 }
1123 if ( classesDirectory == null )
1124 {
1125 throw new NullPointerException( "classesDirectory" );
1126 }
1127
1128 if ( module.getSpecifications() != null )
1129 {
1130 for ( Specification s : module.getSpecifications().getSpecification() )
1131 {
1132 if ( this.isJavaClassDeclaration( s ) )
1133 {
1134 final String classLocation = s.getIdentifier().replace( '.', File.separatorChar ) + ".class";
1135 final File classFile = new File( classesDirectory, classLocation );
1136
1137 if ( this.isLoggable( Level.INFO ) )
1138 {
1139 this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1140 {
1141 classFile.getAbsolutePath()
1142 } ), null );
1143
1144 }
1145
1146 final JavaClass javaClass = this.getJavaClass( classFile );
1147 this.transformClasses( s, marshaller, unmarshaller, javaClass, transformers );
1148 javaClass.dump( classFile );
1149 }
1150 }
1151 }
1152
1153 if ( module.getImplementations() != null )
1154 {
1155 for ( Implementation i : module.getImplementations().getImplementation() )
1156 {
1157 if ( this.isJavaClassDeclaration( i ) )
1158 {
1159 final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
1160 final File classFile = new File( classesDirectory, classLocation );
1161
1162 if ( this.isLoggable( Level.INFO ) )
1163 {
1164 this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1165 {
1166 classFile.getAbsolutePath()
1167 } ), null );
1168
1169 }
1170
1171 final JavaClass javaClass = this.getJavaClass( classFile );
1172 this.transformClasses( i, marshaller, unmarshaller, javaClass, transformers );
1173 javaClass.dump( classFile );
1174 }
1175 }
1176 }
1177 }
1178
1179 /**
1180 * Transforms committed meta-data of compiled Java classes of a given specification of the modules of the instance.
1181 *
1182 * @param specification The specification to process.
1183 * @param marshaller The marshaller to use for transforming classes.
1184 * @param unmarshaller The unmarshaller to use for transforming classes.
1185 * @param javaClass The java class to process.
1186 * @param transformers The transformers to use for transforming the classes.
1187 *
1188 * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
1189 * {@code javaClass} or {@code transformers} is {@code null}.
1190 * @throws IOException if accessing class files fails.
1191 * @throws TransformerException if transforming class files fails.
1192 */
1193 public void transformClasses( final Specification specification, final Marshaller marshaller,
1194 final Unmarshaller unmarshaller, final JavaClass javaClass,
1195 final List<Transformer> transformers ) throws IOException, TransformerException
1196 {
1197 if ( specification == null )
1198 {
1199 throw new NullPointerException( "specification" );
1200 }
1201 if ( marshaller == null )
1202 {
1203 throw new NullPointerException( "marshaller" );
1204 }
1205 if ( unmarshaller == null )
1206 {
1207 throw new NullPointerException( "unmarshaller" );
1208 }
1209 if ( javaClass == null )
1210 {
1211 throw new NullPointerException( "javaClass" );
1212 }
1213 if ( transformers == null )
1214 {
1215 throw new NullPointerException( "transformers" );
1216 }
1217
1218 try
1219 {
1220 Specification decodedSpecification = null;
1221 final ObjectFactory objectFactory = new ObjectFactory();
1222 final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
1223 if ( bytes != null )
1224 {
1225 decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
1226 }
1227
1228 if ( decodedSpecification != null )
1229 {
1230 for ( Transformer transformer : transformers )
1231 {
1232 final JAXBSource source =
1233 new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );
1234
1235 final JAXBResult result = new JAXBResult( unmarshaller );
1236 transformer.transform( source, result );
1237 decodedSpecification = ( (JAXBElement<Specification>) result.getResult() ).getValue();
1238 }
1239
1240 this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
1241 marshaller, objectFactory.createSpecification( decodedSpecification ) ) );
1242
1243 }
1244 }
1245 catch ( final JAXBException e )
1246 {
1247 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1248 }
1249 }
1250
1251 /**
1252 * Transforms committed meta-data of compiled Java classes of a given implementation of the modules of the instance.
1253 *
1254 * @param implementation The implementation to process.
1255 * @param marshaller The marshaller to use for transforming classes.
1256 * @param unmarshaller The unmarshaller to use for transforming classes.
1257 * @param javaClass The java class to process.
1258 * @param transformers The transformers to use for transforming the classes.
1259 *
1260 * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
1261 * {@code javaClass} or {@code transformers} is {@code null}.
1262 * @throws IOException if accessing class files fails.
1263 * @throws TransformerException if transforming class files fails.
1264 */
1265 public void transformClasses( final Implementation implementation, final Marshaller marshaller,
1266 final Unmarshaller unmarshaller, final JavaClass javaClass,
1267 final List<Transformer> transformers ) throws TransformerException, IOException
1268 {
1269 if ( implementation == null )
1270 {
1271 throw new NullPointerException( "implementation" );
1272 }
1273 if ( marshaller == null )
1274 {
1275 throw new NullPointerException( "marshaller" );
1276 }
1277 if ( unmarshaller == null )
1278 {
1279 throw new NullPointerException( "unmarshaller" );
1280 }
1281 if ( javaClass == null )
1282 {
1283 throw new NullPointerException( "javaClass" );
1284 }
1285 if ( transformers == null )
1286 {
1287 throw new NullPointerException( "transformers" );
1288 }
1289
1290 try
1291 {
1292 Dependencies decodedDependencies = null;
1293 byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
1294 if ( bytes != null )
1295 {
1296 decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
1297 }
1298
1299 Messages decodedMessages = null;
1300 bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
1301 if ( bytes != null )
1302 {
1303 decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
1304 }
1305
1306 Properties decodedProperties = null;
1307 bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
1308 if ( bytes != null )
1309 {
1310 decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
1311 }
1312
1313 Specifications decodedSpecifications = null;
1314 bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
1315 if ( bytes != null )
1316 {
1317 decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
1318 }
1319
1320 final ObjectFactory of = new ObjectFactory();
1321 for ( Transformer transformer : transformers )
1322 {
1323 if ( decodedDependencies != null )
1324 {
1325 final JAXBSource source = new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );
1326 final JAXBResult result = new JAXBResult( unmarshaller );
1327 transformer.transform( source, result );
1328 decodedDependencies = ( (JAXBElement<Dependencies>) result.getResult() ).getValue();
1329 }
1330
1331 if ( decodedMessages != null )
1332 {
1333 final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
1334 final JAXBResult result = new JAXBResult( unmarshaller );
1335 transformer.transform( source, result );
1336 decodedMessages = ( (JAXBElement<Messages>) result.getResult() ).getValue();
1337 }
1338
1339 if ( decodedProperties != null )
1340 {
1341 final JAXBSource source = new JAXBSource( marshaller, of.createProperties( decodedProperties ) );
1342 final JAXBResult result = new JAXBResult( unmarshaller );
1343 transformer.transform( source, result );
1344 decodedProperties = ( (JAXBElement<Properties>) result.getResult() ).getValue();
1345 }
1346
1347 if ( decodedSpecifications != null )
1348 {
1349 final JAXBSource source =
1350 new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );
1351
1352 final JAXBResult result = new JAXBResult( unmarshaller );
1353 transformer.transform( source, result );
1354 decodedSpecifications = ( (JAXBElement<Specifications>) result.getResult() ).getValue();
1355 }
1356 }
1357
1358 if ( decodedDependencies != null )
1359 {
1360 this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
1361 marshaller, of.createDependencies( decodedDependencies ) ) );
1362
1363 }
1364
1365 if ( decodedMessages != null )
1366 {
1367 this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
1368 marshaller, of.createMessages( decodedMessages ) ) );
1369
1370 }
1371
1372 if ( decodedProperties != null )
1373 {
1374 this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
1375 marshaller, of.createProperties( decodedProperties ) ) );
1376
1377 }
1378
1379 if ( decodedSpecifications != null )
1380 {
1381 this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
1382 marshaller, of.createSpecifications( decodedSpecifications ) ) );
1383
1384 }
1385 }
1386 catch ( final JAXBException e )
1387 {
1388 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1389 }
1390 }
1391
1392 /**
1393 * Parses a class file.
1394 *
1395 * @param classFile The class file to parse.
1396 *
1397 * @return The parsed class file.
1398 *
1399 * @throws NullPointerException if {@code classFile} is {@code null}.
1400 * @throws IOException if parsing {@code classFile} fails.
1401 *
1402 * @see JavaClass
1403 */
1404 public JavaClass getJavaClass( final File classFile ) throws IOException
1405 {
1406 if ( classFile == null )
1407 {
1408 throw new NullPointerException( "classFile" );
1409 }
1410
1411 return this.getJavaClass( classFile.toURI().toURL(), classFile.getName() );
1412 }
1413
1414 /**
1415 * Parses a class file.
1416 *
1417 * @param url The URL of the class file to parse.
1418 * @param className The name of the class at {@code url}.
1419 *
1420 * @return The parsed class file.
1421 *
1422 * @throws NullPointerException if {@code url} or {@code className} is {@code null}.
1423 * @throws IOException if parsing fails.
1424 *
1425 * @see JavaClass
1426 */
1427 public JavaClass getJavaClass( final URL url, final String className ) throws IOException
1428 {
1429 if ( url == null )
1430 {
1431 throw new NullPointerException( "url" );
1432 }
1433 if ( className == null )
1434 {
1435 throw new NullPointerException( "className" );
1436 }
1437
1438 return this.getJavaClass( url.openStream(), className );
1439 }
1440
1441 /**
1442 * Parses a class file.
1443 *
1444 * @param stream The stream to read the class file from.
1445 * @param className The name of the class to read from {@code stream}.
1446 *
1447 * @return The parsed class file.
1448 *
1449 * @throws NullPointerException if {@code stream} or {@code className} is {@code null}.
1450 * @throws IOException if parsing fails.
1451 *
1452 * @see JavaClass
1453 */
1454 public JavaClass getJavaClass( final InputStream stream, final String className ) throws IOException
1455 {
1456 if ( stream == null )
1457 {
1458 throw new NullPointerException( "stream" );
1459 }
1460 if ( className == null )
1461 {
1462 throw new NullPointerException( "className" );
1463 }
1464
1465 final ClassParser parser = new ClassParser( stream, className );
1466 final JavaClass clazz = parser.parse();
1467 stream.close();
1468 return clazz;
1469 }
1470
1471 /**
1472 * Gets an attribute from a java class.
1473 *
1474 * @param clazz The java class to get an attribute from.
1475 * @param attributeName The name of the attribute to get.
1476 *
1477 * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null} if no such attribute
1478 * exists.
1479 *
1480 * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1481 * @throws IOException if getting the attribute fails.
1482 *
1483 * @see JavaClass#getAttributes()
1484 */
1485 public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
1486 {
1487 if ( clazz == null )
1488 {
1489 throw new NullPointerException( "clazz" );
1490 }
1491 if ( attributeName == null )
1492 {
1493 throw new NullPointerException( "attributeName" );
1494 }
1495
1496 final Attribute[] attributes = clazz.getAttributes();
1497
1498 for ( int i = attributes.length - 1; i >= 0; i-- )
1499 {
1500 final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1501
1502 if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1503 {
1504 final Unknown unknown = (Unknown) attributes[i];
1505 return unknown.getBytes();
1506 }
1507 }
1508
1509 return null;
1510 }
1511
1512 /**
1513 * Adds or updates an attribute in a java class.
1514 *
1515 * @param clazz The class to update.
1516 * @param attributeName The name of the attribute to update.
1517 * @param data The new data of the attribute to update the {@code classFile} with.
1518 *
1519 * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1520 * @throws IOException if updating the class file fails.
1521 *
1522 * @see JavaClass#getAttributes()
1523 */
1524 public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
1525 throws IOException
1526 {
1527 if ( clazz == null )
1528 {
1529 throw new NullPointerException( "clazz" );
1530 }
1531 if ( attributeName == null )
1532 {
1533 throw new NullPointerException( "attributeName" );
1534 }
1535
1536 /*
1537 The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
1538
1539 A Java virtual machine implementation is required to silently ignore any
1540 or all attributes in the attributes table of a ClassFile structure that
1541 it does not recognize. Attributes not defined in this specification are
1542 not allowed to affect the semantics of the class file, but only to
1543 provide additional descriptive information (ยง4.7.1).
1544 */
1545 Attribute[] attributes = clazz.getAttributes();
1546
1547 int attributeIndex = -1;
1548 int nameIndex = -1;
1549
1550 for ( int i = attributes.length - 1; i >= 0; i-- )
1551 {
1552 final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1553
1554 if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1555 {
1556 attributeIndex = i;
1557 nameIndex = attributes[i].getNameIndex();
1558 }
1559 }
1560
1561 if ( nameIndex == -1 )
1562 {
1563 final Constant[] pool = clazz.getConstantPool().getConstantPool();
1564 final Constant[] tmp = new Constant[ pool.length + 1 ];
1565 System.arraycopy( pool, 0, tmp, 0, pool.length );
1566 tmp[pool.length] = new ConstantUtf8( attributeName );
1567 nameIndex = pool.length;
1568 clazz.setConstantPool( new ConstantPool( tmp ) );
1569 }
1570
1571 final Unknown unknown = new Unknown( nameIndex, data.length, data, clazz.getConstantPool() );
1572
1573 if ( attributeIndex == -1 )
1574 {
1575 final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
1576 System.arraycopy( attributes, 0, tmp, 0, attributes.length );
1577 tmp[attributes.length] = unknown;
1578 attributes = tmp;
1579 }
1580 else
1581 {
1582 attributes[attributeIndex] = unknown;
1583 }
1584
1585 clazz.setAttributes( attributes );
1586 }
1587
1588 /**
1589 * Encodes a model object to a byte array.
1590 *
1591 * @param marshaller The marshaller to use for encoding the object.
1592 * @param modelObject The model object to encode.
1593 *
1594 * @return GZIP compressed XML document for {@code modelObject}.
1595 *
1596 * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
1597 * @throws IOException if encoding {@code modelObject} fails.
1598 */
1599 public byte[] encodeModelObject( final Marshaller marshaller,
1600 final JAXBElement<? extends ModelObject> modelObject ) throws IOException
1601 {
1602 if ( marshaller == null )
1603 {
1604 throw new NullPointerException( "marshaller" );
1605 }
1606 if ( modelObject == null )
1607 {
1608 throw new NullPointerException( "modelObject" );
1609 }
1610
1611 try
1612 {
1613 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1614 final GZIPOutputStream out = new GZIPOutputStream( baos );
1615 marshaller.marshal( modelObject, out );
1616 out.close();
1617 return baos.toByteArray();
1618 }
1619 catch ( final JAXBException e )
1620 {
1621 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1622 }
1623 }
1624
1625 /**
1626 * Decodes a model object from a byte array.
1627 *
1628 * @param unmarshaller The unmarshaller to use for decoding the object.
1629 * @param bytes The encoded model object to decode.
1630 * @param type The type of the encoded model object.
1631 * @param <T> The type of the decoded model object.
1632 *
1633 * @return Model object decoded from {@code bytes}.
1634 *
1635 * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
1636 * @throws IOException if decoding {@code bytes} fails.
1637 */
1638 public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
1639 final Class<T> type ) throws IOException
1640 {
1641 if ( unmarshaller == null )
1642 {
1643 throw new NullPointerException( "unmarshaller" );
1644 }
1645 if ( bytes == null )
1646 {
1647 throw new NullPointerException( "bytes" );
1648 }
1649 if ( type == null )
1650 {
1651 throw new NullPointerException( "type" );
1652 }
1653
1654 try
1655 {
1656 final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
1657 final GZIPInputStream in = new GZIPInputStream( bais );
1658 final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
1659 in.close();
1660 return element.getValue();
1661 }
1662 catch ( final JAXBException e )
1663 {
1664 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1665 }
1666 }
1667
1668 private String getMessage( final String key, final Object args )
1669 {
1670 final ResourceBundle b = ResourceBundle.getBundle( JavaClasses.class.getName().replace( '.', '/' ) );
1671 final MessageFormat f = new MessageFormat( b.getString( key ) );
1672 return f.format( args );
1673 }
1674
1675 }