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 898 2009-11-02 06:51:58Z 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 898 2009-11-02 06:51:58Z 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().equals( specification.getClazz() ) )
679 {
680 report.getDetails().add( new ModelObjectValidationReport.Detail(
681 "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
682 this.getMessage( "illegalSpecificationClass", new Object[]
683 {
684 decoded.getIdentifier(), specification.getClazz(), decoded.getClazz()
685 } ) ) );
686
687 }
688 }
689 else if ( this.isLoggable( Level.WARNING ) )
690 {
691 this.log( Level.WARNING, this.getMessage( "cannotValidateSpecification", new Object[]
692 {
693 specification.getIdentifier(), Specification.class.getName()
694 } ), null );
695
696 }
697
698 return report;
699 }
700
701 /**
702 * Validates compiled Java classes against a given implementation of the modules of the instance.
703 *
704 * @param implementation The implementation to process.
705 * @param unmarshaller The unmarshaller to use for validating classes.
706 * @param javaClass The class to validate.
707 *
708 * @return The report of the validation.
709 *
710 * @throws NullPointerException if {@code implementation}, {@code unmarshaller} or {@code javaClass} is
711 * {@code null}.
712 * @throws IOException if reading class files fails.
713 */
714 public ModelObjectValidationReport validateClasses( final Implementation implementation,
715 final Unmarshaller unmarshaller, final JavaClass javaClass )
716 throws IOException
717 {
718 if ( implementation == null )
719 {
720 throw new NullPointerException( "implementation" );
721 }
722 if ( unmarshaller == null )
723 {
724 throw new NullPointerException( "unmarshaller" );
725 }
726 if ( javaClass == null )
727 {
728 throw new NullPointerException( "javaClass" );
729 }
730
731 if ( this.isLoggable( Level.INFO ) )
732 {
733 this.log( Level.INFO, this.getMessage( "validatingImplementation", new Object[]
734 {
735 implementation.getIdentifier()
736 } ), null );
737
738 }
739
740 final ModelObjectValidationReport report = new ModelObjectValidationReport(
741 new ObjectFactory().createImplementation( implementation ) );
742
743 try
744 {
745 Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
746 if ( dependencies == null )
747 {
748 dependencies = new Dependencies();
749 }
750
751 Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
752 if ( properties == null )
753 {
754 properties = new Properties();
755 }
756
757 Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
758 if ( messages == null )
759 {
760 messages = new Messages();
761 }
762
763 Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
764 if ( specifications == null )
765 {
766 specifications = new Specifications();
767 }
768
769 Dependencies decodedDependencies = null;
770 byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
771 if ( bytes != null )
772 {
773 decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
774 }
775
776 Properties decodedProperties = null;
777 bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
778 if ( bytes != null )
779 {
780 decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
781 }
782
783 Messages decodedMessages = null;
784 bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
785 if ( bytes != null )
786 {
787 decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
788 }
789
790 Specifications decodedSpecifications = null;
791 bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
792 if ( bytes != null )
793 {
794 decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
795 }
796
797 if ( decodedDependencies != null )
798 {
799 for ( Dependency decodedDependency : decodedDependencies.getDependency() )
800 {
801 final Dependency dependency = dependencies.getDependency( decodedDependency.getName() );
802
803 if ( dependency == null )
804 {
805 report.getDetails().add( new ModelObjectValidationReport.Detail(
806 "CLASS_MISSING_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
807 this.getMessage( "missingDependency", new Object[]
808 {
809 implementation.getIdentifier(), decodedDependency.getName()
810 } ) ) );
811
812 }
813
814 final Specification s = this.getModules().getSpecification( decodedDependency.getIdentifier() );
815
816 if ( s != null && s.getVersion() != null && decodedDependency.getVersion() != null &&
817 VersionParser.compare( decodedDependency.getVersion(), s.getVersion() ) > 0 )
818 {
819 final Module moduleOfSpecification =
820 this.getModules().getModuleOfSpecification( s.getIdentifier() );
821
822 final Module moduleOfImplementation =
823 this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
824
825 report.getDetails().add( new ModelObjectValidationReport.Detail(
826 "CLASS_INCOMPATIBLE_IMPLEMENTATION_DEPENDENCY", Level.SEVERE,
827 this.getMessage( "incompatibleDependency", new Object[]
828 {
829 javaClass.getClassName(), moduleOfImplementation == null
830 ? "<>" : moduleOfImplementation.getName(),
831 s.getIdentifier(), moduleOfSpecification == null
832 ? "<>" : moduleOfSpecification.getName(),
833 decodedDependency.getVersion(), s.getVersion()
834 } ) ) );
835
836 }
837 }
838 }
839 else if ( this.isLoggable( Level.WARNING ) )
840 {
841 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
842 {
843 implementation.getIdentifier(), Dependencies.class.getName()
844 } ), null );
845
846 }
847
848 if ( decodedProperties != null )
849 {
850 for ( Property decodedProperty : decodedProperties.getProperty() )
851 {
852 final Property property = properties.getProperty( decodedProperty.getName() );
853
854 if ( property == null )
855 {
856 report.getDetails().add( new ModelObjectValidationReport.Detail(
857 "CLASS_MISSING_IMPLEMENTATION_PROPERTY", Level.SEVERE,
858 this.getMessage( "missingProperty", new Object[]
859 {
860 implementation.getIdentifier(), decodedProperty.getName()
861 } ) ) );
862
863 }
864 else
865 {
866 if ( decodedProperty.getType() == null
867 ? property.getType() != null
868 : !decodedProperty.getType().equals( property.getType() ) )
869 {
870 report.getDetails().add( new ModelObjectValidationReport.Detail(
871 "CLASS_ILLEGAL_IMPLEMENTATION_PROPERTY", Level.SEVERE,
872 this.getMessage( "illegalPropertyType", new Object[]
873 {
874 implementation.getIdentifier(), decodedProperty.getName(),
875 property.getType() == null ? "default" : property.getType(),
876 decodedProperty.getType() == null ? "default" : decodedProperty.getType()
877 } ) ) );
878
879 }
880 }
881 }
882 }
883 else if ( this.isLoggable( Level.WARNING ) )
884 {
885 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
886 {
887 implementation.getIdentifier(), Properties.class.getName()
888 } ), null );
889
890 }
891
892 if ( decodedMessages != null )
893 {
894 for ( Message decodedMessage : decodedMessages.getMessage() )
895 {
896 final Message message = messages.getMessage( decodedMessage.getName() );
897
898 if ( message == null )
899 {
900 report.getDetails().add( new ModelObjectValidationReport.Detail(
901 "CLASS_MISSING_IMPLEMENTATION_MESSAGE", Level.SEVERE,
902 this.getMessage( "missingMessage", new Object[]
903 {
904 implementation.getIdentifier(), decodedMessage.getName()
905 } ) ) );
906
907 }
908 }
909 }
910 else if ( this.isLoggable( Level.WARNING ) )
911 {
912 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
913 {
914 implementation.getIdentifier(), Messages.class.getName()
915 } ), null );
916
917 }
918
919 if ( decodedSpecifications != null )
920 {
921 for ( Specification decodedSpecification : decodedSpecifications.getSpecification() )
922 {
923 final Specification specification =
924 this.getModules().getSpecification( decodedSpecification.getIdentifier() );
925
926 if ( specification == null )
927 {
928 report.getDetails().add( new ModelObjectValidationReport.Detail(
929 "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
930 this.getMessage( "missingSpecification", new Object[]
931 {
932 implementation.getIdentifier(), decodedSpecification.getIdentifier()
933 } ) ) );
934
935 }
936 else
937 {
938 if ( decodedSpecification.getMultiplicity() != specification.getMultiplicity() )
939 {
940 report.getDetails().add( new ModelObjectValidationReport.Detail(
941 "CLASS_ILLEGAL_SPECIFICATION_MULTIPLICITY", Level.SEVERE,
942 this.getMessage( "illegalMultiplicity", new Object[]
943 {
944 specification.getIdentifier(), specification.getMultiplicity().value(),
945 decodedSpecification.getMultiplicity().value()
946 } ) ) );
947
948 }
949
950 if ( decodedSpecification.getScope() == null
951 ? specification.getScope() != null
952 : !decodedSpecification.getScope().equals( specification.getScope() ) )
953 {
954 report.getDetails().add( new ModelObjectValidationReport.Detail(
955 "CLASS_ILLEGAL_SPECIFICATION_SCOPE", Level.SEVERE,
956 this.getMessage( "illegalScope", new Object[]
957 {
958 decodedSpecification.getIdentifier(),
959 specification.getScope() == null
960 ? "Multiton" : specification.getScope(),
961 decodedSpecification.getScope() == null
962 ? "Multiton" : decodedSpecification.getScope()
963 } ) ) );
964
965 }
966
967 if ( !decodedSpecification.getClazz().equals( specification.getClazz() ) )
968 {
969 report.getDetails().add( new ModelObjectValidationReport.Detail(
970 "CLASS_ILLEGAL_SPECIFICATION_CLASS", Level.SEVERE,
971 this.getMessage( "illegalSpecificationClass", new Object[]
972 {
973 decodedSpecification.getIdentifier(), specification.getClazz(),
974 decodedSpecification.getClazz()
975 } ) ) );
976
977 }
978 }
979 }
980
981 for ( SpecificationReference decodedReference : decodedSpecifications.getReference() )
982 {
983 final Specification specification =
984 specifications.getSpecification( decodedReference.getIdentifier() );
985
986 if ( specification == null )
987 {
988 report.getDetails().add( new ModelObjectValidationReport.Detail(
989 "CLASS_MISSING_SPECIFICATION", Level.SEVERE,
990 this.getMessage( "missingSpecification", new Object[]
991 {
992 implementation.getIdentifier(), decodedReference.getIdentifier()
993 } ) ) );
994
995 }
996 else if ( decodedReference.getVersion() != null && specification.getVersion() != null &&
997 VersionParser.compare( decodedReference.getVersion(), specification.getVersion() ) != 0 )
998 {
999 final Module moduleOfSpecification =
1000 this.getModules().getModuleOfSpecification( decodedReference.getIdentifier() );
1001
1002 final Module moduleOfImplementation =
1003 this.getModules().getModuleOfImplementation( implementation.getIdentifier() );
1004
1005 report.getDetails().add( new ModelObjectValidationReport.Detail(
1006 "CLASS_INCOMPATIBLE_IMPLEMENTATION", Level.SEVERE,
1007 this.getMessage( "incompatibleImplementation", new Object[]
1008 {
1009 javaClass.getClassName(), moduleOfImplementation == null
1010 ? "<>" : moduleOfImplementation.getName(),
1011 specification.getIdentifier(), moduleOfSpecification == null
1012 ? "<>" : moduleOfSpecification.getName(),
1013 decodedReference.getVersion(), specification.getVersion()
1014 } ) ) );
1015
1016 }
1017 }
1018 }
1019 else if ( this.isLoggable( Level.WARNING ) )
1020 {
1021 this.log( Level.WARNING, this.getMessage( "cannotValidateImplementation", new Object[]
1022 {
1023 implementation.getIdentifier(), Specifications.class.getName()
1024 } ), null );
1025
1026 }
1027
1028 return report;
1029 }
1030 catch ( final ParseException e )
1031 {
1032 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1033 }
1034 catch ( final TokenMgrError e )
1035 {
1036 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1037 }
1038 }
1039
1040 /**
1041 * Transforms committed meta-data of compiled Java classes of the modules of the instance.
1042 *
1043 * @param marshaller The marshaller to use for transforming classes.
1044 * @param unmarshaller The unmarshaller to use for transforming classes.
1045 * @param classesDirectory The directory holding the compiled class files.
1046 * @param transformers The transformers to use for transforming the classes.
1047 *
1048 * @throws NullPointerException if {@code marshaller}, {@code unmarshaller}, {@code classesDirectory} or
1049 * {@code transformers} is {@code null}.
1050 * @throws IOException if accessing class files fails.
1051 * @throws TransformerException if transforming class files fails.
1052 *
1053 * @see #transformClasses(org.jomc.model.Module, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, java.io.File, java.util.List)
1054 */
1055 public void transformClasses( final Marshaller marshaller, final Unmarshaller unmarshaller,
1056 final File classesDirectory, final List<Transformer> transformers )
1057 throws IOException, TransformerException
1058 {
1059 if ( marshaller == null )
1060 {
1061 throw new NullPointerException( "marshaller" );
1062 }
1063 if ( unmarshaller == null )
1064 {
1065 throw new NullPointerException( "unmarshaller" );
1066 }
1067 if ( transformers == null )
1068 {
1069 throw new NullPointerException( "transformers" );
1070 }
1071 if ( classesDirectory == null )
1072 {
1073 throw new NullPointerException( "classesDirectory" );
1074 }
1075
1076 for ( Module m : this.getModules().getModule() )
1077 {
1078 this.transformClasses( m, marshaller, unmarshaller, classesDirectory, transformers );
1079 }
1080 }
1081
1082 /**
1083 * Transforms committed meta-data of compiled Java classes of a given module of the modules of the instance.
1084 *
1085 * @param module The module to process.
1086 * @param marshaller The marshaller to use for transforming classes.
1087 * @param unmarshaller The unmarshaller to use for transforming classes.
1088 * @param classesDirectory The directory holding the compiled class files.
1089 * @param transformers The transformers to use for transforming the classes.
1090 *
1091 * @throws NullPointerException if {@code module}, {@code marshaller}, {@code unmarshaller},
1092 * {@code classesDirectory} or {@code transformers} is {@code null}.
1093 * @throws IOException if accessing class files fails.
1094 * @throws TransformerException if transforming class files fails.
1095 *
1096 * @see #transformClasses(org.jomc.model.Specification, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1097 * @see #transformClasses(org.jomc.model.Implementation, javax.xml.bind.Marshaller, javax.xml.bind.Unmarshaller, org.apache.bcel.classfile.JavaClass, java.util.List)
1098 */
1099 public void transformClasses( final Module module, final Marshaller marshaller, final Unmarshaller unmarshaller,
1100 final File classesDirectory, final List<Transformer> transformers )
1101 throws IOException, TransformerException
1102 {
1103 if ( module == null )
1104 {
1105 throw new NullPointerException( "module" );
1106 }
1107 if ( marshaller == null )
1108 {
1109 throw new NullPointerException( "marshaller" );
1110 }
1111 if ( unmarshaller == null )
1112 {
1113 throw new NullPointerException( "unmarshaller" );
1114 }
1115 if ( transformers == null )
1116 {
1117 throw new NullPointerException( "transformers" );
1118 }
1119 if ( classesDirectory == null )
1120 {
1121 throw new NullPointerException( "classesDirectory" );
1122 }
1123
1124 if ( module.getSpecifications() != null )
1125 {
1126 for ( Specification s : module.getSpecifications().getSpecification() )
1127 {
1128 if ( this.isJavaClassDeclaration( s ) )
1129 {
1130 final String classLocation = s.getIdentifier().replace( '.', File.separatorChar ) + ".class";
1131 final File classFile = new File( classesDirectory, classLocation );
1132
1133 if ( this.isLoggable( Level.INFO ) )
1134 {
1135 this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1136 {
1137 classFile.getAbsolutePath()
1138 } ), null );
1139
1140 }
1141
1142 final JavaClass javaClass = this.getJavaClass( classFile );
1143 this.transformClasses( s, marshaller, unmarshaller, javaClass, transformers );
1144 javaClass.dump( classFile );
1145 }
1146 }
1147 }
1148
1149 if ( module.getImplementations() != null )
1150 {
1151 for ( Implementation i : module.getImplementations().getImplementation() )
1152 {
1153 if ( this.isJavaClassDeclaration( i ) )
1154 {
1155 final String classLocation = i.getClazz().replace( '.', File.separatorChar ) + ".class";
1156 final File classFile = new File( classesDirectory, classLocation );
1157
1158 if ( this.isLoggable( Level.INFO ) )
1159 {
1160 this.log( Level.INFO, this.getMessage( "transforming", new Object[]
1161 {
1162 classFile.getAbsolutePath()
1163 } ), null );
1164
1165 }
1166
1167 final JavaClass javaClass = this.getJavaClass( classFile );
1168 this.transformClasses( i, marshaller, unmarshaller, javaClass, transformers );
1169 javaClass.dump( classFile );
1170 }
1171 }
1172 }
1173 }
1174
1175 /**
1176 * Transforms committed meta-data of compiled Java classes of a given specification of the modules of the instance.
1177 *
1178 * @param specification The specification to process.
1179 * @param marshaller The marshaller to use for transforming classes.
1180 * @param unmarshaller The unmarshaller to use for transforming classes.
1181 * @param javaClass The java class to process.
1182 * @param transformers The transformers to use for transforming the classes.
1183 *
1184 * @throws NullPointerException if {@code specification}, {@code marshaller}, {@code unmarshaller},
1185 * {@code javaClass} or {@code transformers} is {@code null}.
1186 * @throws IOException if accessing class files fails.
1187 * @throws TransformerException if transforming class files fails.
1188 */
1189 public void transformClasses( final Specification specification, final Marshaller marshaller,
1190 final Unmarshaller unmarshaller, final JavaClass javaClass,
1191 final List<Transformer> transformers ) throws IOException, TransformerException
1192 {
1193 if ( specification == null )
1194 {
1195 throw new NullPointerException( "specification" );
1196 }
1197 if ( marshaller == null )
1198 {
1199 throw new NullPointerException( "marshaller" );
1200 }
1201 if ( unmarshaller == null )
1202 {
1203 throw new NullPointerException( "unmarshaller" );
1204 }
1205 if ( javaClass == null )
1206 {
1207 throw new NullPointerException( "javaClass" );
1208 }
1209 if ( transformers == null )
1210 {
1211 throw new NullPointerException( "transformers" );
1212 }
1213
1214 try
1215 {
1216 Specification decodedSpecification = null;
1217 final ObjectFactory objectFactory = new ObjectFactory();
1218 final byte[] bytes = this.getClassfileAttribute( javaClass, Specification.class.getName() );
1219 if ( bytes != null )
1220 {
1221 decodedSpecification = this.decodeModelObject( unmarshaller, bytes, Specification.class );
1222 }
1223
1224 if ( decodedSpecification != null )
1225 {
1226 for ( Transformer transformer : transformers )
1227 {
1228 final JAXBSource source =
1229 new JAXBSource( marshaller, objectFactory.createSpecification( decodedSpecification ) );
1230
1231 final JAXBResult result = new JAXBResult( unmarshaller );
1232 transformer.transform( source, result );
1233 decodedSpecification = ( (JAXBElement<Specification>) result.getResult() ).getValue();
1234 }
1235
1236 this.setClassfileAttribute( javaClass, Specification.class.getName(), this.encodeModelObject(
1237 marshaller, objectFactory.createSpecification( decodedSpecification ) ) );
1238
1239 }
1240 }
1241 catch ( final JAXBException e )
1242 {
1243 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1244 }
1245 }
1246
1247 /**
1248 * Transforms committed meta-data of compiled Java classes of a given implementation of the modules of the instance.
1249 *
1250 * @param implementation The implementation to process.
1251 * @param marshaller The marshaller to use for transforming classes.
1252 * @param unmarshaller The unmarshaller to use for transforming classes.
1253 * @param javaClass The java class to process.
1254 * @param transformers The transformers to use for transforming the classes.
1255 *
1256 * @throws NullPointerException if {@code implementation}, {@code marshaller}, {@code unmarshaller},
1257 * {@code javaClass} or {@code transformers} is {@code null}.
1258 * @throws IOException if accessing class files fails.
1259 * @throws TransformerException if transforming class files fails.
1260 */
1261 public void transformClasses( final Implementation implementation, final Marshaller marshaller,
1262 final Unmarshaller unmarshaller, final JavaClass javaClass,
1263 final List<Transformer> transformers ) throws TransformerException, IOException
1264 {
1265 if ( implementation == null )
1266 {
1267 throw new NullPointerException( "implementation" );
1268 }
1269 if ( marshaller == null )
1270 {
1271 throw new NullPointerException( "marshaller" );
1272 }
1273 if ( unmarshaller == null )
1274 {
1275 throw new NullPointerException( "unmarshaller" );
1276 }
1277 if ( javaClass == null )
1278 {
1279 throw new NullPointerException( "javaClass" );
1280 }
1281 if ( transformers == null )
1282 {
1283 throw new NullPointerException( "transformers" );
1284 }
1285
1286 try
1287 {
1288 Dependencies decodedDependencies = null;
1289 byte[] bytes = this.getClassfileAttribute( javaClass, Dependencies.class.getName() );
1290 if ( bytes != null )
1291 {
1292 decodedDependencies = this.decodeModelObject( unmarshaller, bytes, Dependencies.class );
1293 }
1294
1295 Messages decodedMessages = null;
1296 bytes = this.getClassfileAttribute( javaClass, Messages.class.getName() );
1297 if ( bytes != null )
1298 {
1299 decodedMessages = this.decodeModelObject( unmarshaller, bytes, Messages.class );
1300 }
1301
1302 Properties decodedProperties = null;
1303 bytes = this.getClassfileAttribute( javaClass, Properties.class.getName() );
1304 if ( bytes != null )
1305 {
1306 decodedProperties = this.decodeModelObject( unmarshaller, bytes, Properties.class );
1307 }
1308
1309 Specifications decodedSpecifications = null;
1310 bytes = this.getClassfileAttribute( javaClass, Specifications.class.getName() );
1311 if ( bytes != null )
1312 {
1313 decodedSpecifications = this.decodeModelObject( unmarshaller, bytes, Specifications.class );
1314 }
1315
1316 final ObjectFactory of = new ObjectFactory();
1317 for ( Transformer transformer : transformers )
1318 {
1319 if ( decodedDependencies != null )
1320 {
1321 final JAXBSource source = new JAXBSource( marshaller, of.createDependencies( decodedDependencies ) );
1322 final JAXBResult result = new JAXBResult( unmarshaller );
1323 transformer.transform( source, result );
1324 decodedDependencies = ( (JAXBElement<Dependencies>) result.getResult() ).getValue();
1325 }
1326
1327 if ( decodedMessages != null )
1328 {
1329 final JAXBSource source = new JAXBSource( marshaller, of.createMessages( decodedMessages ) );
1330 final JAXBResult result = new JAXBResult( unmarshaller );
1331 transformer.transform( source, result );
1332 decodedMessages = ( (JAXBElement<Messages>) result.getResult() ).getValue();
1333 }
1334
1335 if ( decodedProperties != null )
1336 {
1337 final JAXBSource source = new JAXBSource( marshaller, of.createProperties( decodedProperties ) );
1338 final JAXBResult result = new JAXBResult( unmarshaller );
1339 transformer.transform( source, result );
1340 decodedProperties = ( (JAXBElement<Properties>) result.getResult() ).getValue();
1341 }
1342
1343 if ( decodedSpecifications != null )
1344 {
1345 final JAXBSource source =
1346 new JAXBSource( marshaller, of.createSpecifications( decodedSpecifications ) );
1347
1348 final JAXBResult result = new JAXBResult( unmarshaller );
1349 transformer.transform( source, result );
1350 decodedSpecifications = ( (JAXBElement<Specifications>) result.getResult() ).getValue();
1351 }
1352 }
1353
1354 if ( decodedDependencies != null )
1355 {
1356 this.setClassfileAttribute( javaClass, Dependencies.class.getName(), this.encodeModelObject(
1357 marshaller, of.createDependencies( decodedDependencies ) ) );
1358
1359 }
1360
1361 if ( decodedMessages != null )
1362 {
1363 this.setClassfileAttribute( javaClass, Messages.class.getName(), this.encodeModelObject(
1364 marshaller, of.createMessages( decodedMessages ) ) );
1365
1366 }
1367
1368 if ( decodedProperties != null )
1369 {
1370 this.setClassfileAttribute( javaClass, Properties.class.getName(), this.encodeModelObject(
1371 marshaller, of.createProperties( decodedProperties ) ) );
1372
1373 }
1374
1375 if ( decodedSpecifications != null )
1376 {
1377 this.setClassfileAttribute( javaClass, Specifications.class.getName(), this.encodeModelObject(
1378 marshaller, of.createSpecifications( decodedSpecifications ) ) );
1379
1380 }
1381 }
1382 catch ( final JAXBException e )
1383 {
1384 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1385 }
1386 }
1387
1388 /**
1389 * Parses a class file.
1390 *
1391 * @param classFile The class file to parse.
1392 *
1393 * @return The parsed class file.
1394 *
1395 * @throws NullPointerException if {@code classFile} is {@code null}.
1396 * @throws IOException if parsing {@code classFile} fails.
1397 *
1398 * @see JavaClass
1399 */
1400 public JavaClass getJavaClass( final File classFile ) throws IOException
1401 {
1402 if ( classFile == null )
1403 {
1404 throw new NullPointerException( "classFile" );
1405 }
1406
1407 return this.getJavaClass( classFile.toURI().toURL(), classFile.getName() );
1408 }
1409
1410 /**
1411 * Parses a class file.
1412 *
1413 * @param url The URL of the class file to parse.
1414 * @param className The name of the class at {@code url}.
1415 *
1416 * @return The parsed class file.
1417 *
1418 * @throws NullPointerException if {@code url} or {@code className} is {@code null}.
1419 * @throws IOException if parsing fails.
1420 *
1421 * @see JavaClass
1422 */
1423 public JavaClass getJavaClass( final URL url, final String className ) throws IOException
1424 {
1425 if ( url == null )
1426 {
1427 throw new NullPointerException( "url" );
1428 }
1429 if ( className == null )
1430 {
1431 throw new NullPointerException( "className" );
1432 }
1433
1434 return this.getJavaClass( url.openStream(), className );
1435 }
1436
1437 /**
1438 * Parses a class file.
1439 *
1440 * @param stream The stream to read the class file from.
1441 * @param className The name of the class to read from {@code stream}.
1442 *
1443 * @return The parsed class file.
1444 *
1445 * @throws NullPointerException if {@code stream} or {@code className} is {@code null}.
1446 * @throws IOException if parsing fails.
1447 *
1448 * @see JavaClass
1449 */
1450 public JavaClass getJavaClass( final InputStream stream, final String className ) throws IOException
1451 {
1452 if ( stream == null )
1453 {
1454 throw new NullPointerException( "stream" );
1455 }
1456 if ( className == null )
1457 {
1458 throw new NullPointerException( "className" );
1459 }
1460
1461 final ClassParser parser = new ClassParser( stream, className );
1462 final JavaClass clazz = parser.parse();
1463 stream.close();
1464 return clazz;
1465 }
1466
1467 /**
1468 * Gets an attribute from a java class.
1469 *
1470 * @param clazz The java class to get an attribute from.
1471 * @param attributeName The name of the attribute to get.
1472 *
1473 * @return The value of attribute {@code attributeName} of {@code clazz} or {@code null} if no such attribute
1474 * exists.
1475 *
1476 * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1477 * @throws IOException if getting the attribute fails.
1478 *
1479 * @see JavaClass#getAttributes()
1480 */
1481 public byte[] getClassfileAttribute( final JavaClass clazz, final String attributeName ) throws IOException
1482 {
1483 if ( clazz == null )
1484 {
1485 throw new NullPointerException( "clazz" );
1486 }
1487 if ( attributeName == null )
1488 {
1489 throw new NullPointerException( "attributeName" );
1490 }
1491
1492 final Attribute[] attributes = clazz.getAttributes();
1493
1494 for ( int i = attributes.length - 1; i >= 0; i-- )
1495 {
1496 final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1497
1498 if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1499 {
1500 final Unknown unknown = (Unknown) attributes[i];
1501 return unknown.getBytes();
1502 }
1503 }
1504
1505 return null;
1506 }
1507
1508 /**
1509 * Adds or updates an attribute in a java class.
1510 *
1511 * @param clazz The class to update.
1512 * @param attributeName The name of the attribute to update.
1513 * @param data The new data of the attribute to update the {@code classFile} with.
1514 *
1515 * @throws NullPointerException if {@code clazz} or {@code attributeName} is {@code null}.
1516 * @throws IOException if updating the class file fails.
1517 *
1518 * @see JavaClass#getAttributes()
1519 */
1520 public void setClassfileAttribute( final JavaClass clazz, final String attributeName, final byte[] data )
1521 throws IOException
1522 {
1523 if ( clazz == null )
1524 {
1525 throw new NullPointerException( "clazz" );
1526 }
1527 if ( attributeName == null )
1528 {
1529 throw new NullPointerException( "attributeName" );
1530 }
1531
1532 /*
1533 The JavaTM Virtual Machine Specification - Second Edition - Chapter 4.1
1534
1535 A Java virtual machine implementation is required to silently ignore any
1536 or all attributes in the attributes table of a ClassFile structure that
1537 it does not recognize. Attributes not defined in this specification are
1538 not allowed to affect the semantics of the class file, but only to
1539 provide additional descriptive information (ยง4.7.1).
1540 */
1541 Attribute[] attributes = clazz.getAttributes();
1542
1543 int attributeIndex = -1;
1544 int nameIndex = -1;
1545
1546 for ( int i = attributes.length - 1; i >= 0; i-- )
1547 {
1548 final Constant constant = clazz.getConstantPool().getConstant( attributes[i].getNameIndex() );
1549
1550 if ( constant instanceof ConstantUtf8 && attributeName.equals( ( (ConstantUtf8) constant ).getBytes() ) )
1551 {
1552 attributeIndex = i;
1553 nameIndex = attributes[i].getNameIndex();
1554 }
1555 }
1556
1557 if ( nameIndex == -1 )
1558 {
1559 final Constant[] pool = clazz.getConstantPool().getConstantPool();
1560 final Constant[] tmp = new Constant[ pool.length + 1 ];
1561 System.arraycopy( pool, 0, tmp, 0, pool.length );
1562 tmp[pool.length] = new ConstantUtf8( attributeName );
1563 nameIndex = pool.length;
1564 clazz.setConstantPool( new ConstantPool( tmp ) );
1565 }
1566
1567 final Unknown unknown = new Unknown( nameIndex, data.length, data, clazz.getConstantPool() );
1568
1569 if ( attributeIndex == -1 )
1570 {
1571 final Attribute[] tmp = new Attribute[ attributes.length + 1 ];
1572 System.arraycopy( attributes, 0, tmp, 0, attributes.length );
1573 tmp[attributes.length] = unknown;
1574 attributes = tmp;
1575 }
1576 else
1577 {
1578 attributes[attributeIndex] = unknown;
1579 }
1580
1581 clazz.setAttributes( attributes );
1582 }
1583
1584 /**
1585 * Encodes a model object to a byte array.
1586 *
1587 * @param marshaller The marshaller to use for encoding the object.
1588 * @param modelObject The model object to encode.
1589 *
1590 * @return GZIP compressed XML document for {@code modelObject}.
1591 *
1592 * @throws NullPointerException if {@code marshaller} or {@code modelObject} is {@code null}.
1593 * @throws IOException if encoding {@code modelObject} fails.
1594 */
1595 public byte[] encodeModelObject( final Marshaller marshaller,
1596 final JAXBElement<? extends ModelObject> modelObject ) throws IOException
1597 {
1598 if ( marshaller == null )
1599 {
1600 throw new NullPointerException( "marshaller" );
1601 }
1602 if ( modelObject == null )
1603 {
1604 throw new NullPointerException( "modelObject" );
1605 }
1606
1607 try
1608 {
1609 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1610 final GZIPOutputStream out = new GZIPOutputStream( baos );
1611 marshaller.marshal( modelObject, out );
1612 out.close();
1613 return baos.toByteArray();
1614 }
1615 catch ( final JAXBException e )
1616 {
1617 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1618 }
1619 }
1620
1621 /**
1622 * Decodes a model object from a byte array.
1623 *
1624 * @param unmarshaller The unmarshaller to use for decoding the object.
1625 * @param bytes The encoded model object to decode.
1626 * @param type The type of the encoded model object.
1627 * @param <T> The type of the decoded model object.
1628 *
1629 * @return Model object decoded from {@code bytes}.
1630 *
1631 * @throws NullPointerException if {@code unmarshaller}, {@code bytes} or {@code type} is {@code null}.
1632 * @throws IOException if decoding {@code bytes} fails.
1633 */
1634 public <T extends ModelObject> T decodeModelObject( final Unmarshaller unmarshaller, final byte[] bytes,
1635 final Class<T> type ) throws IOException
1636 {
1637 if ( unmarshaller == null )
1638 {
1639 throw new NullPointerException( "unmarshaller" );
1640 }
1641 if ( bytes == null )
1642 {
1643 throw new NullPointerException( "bytes" );
1644 }
1645 if ( type == null )
1646 {
1647 throw new NullPointerException( "type" );
1648 }
1649
1650 try
1651 {
1652 final ByteArrayInputStream bais = new ByteArrayInputStream( bytes );
1653 final GZIPInputStream in = new GZIPInputStream( bais );
1654 final JAXBElement<T> element = (JAXBElement<T>) unmarshaller.unmarshal( in );
1655 in.close();
1656 return element.getValue();
1657 }
1658 catch ( final JAXBException e )
1659 {
1660 throw (IOException) new IOException( e.getMessage() ).initCause( e );
1661 }
1662 }
1663
1664 private String getMessage( final String key, final Object args )
1665 {
1666 final ResourceBundle b = ResourceBundle.getBundle( JavaClasses.class.getName().replace( '.', '/' ) );
1667 final MessageFormat f = new MessageFormat( b.getString( key ) );
1668 return f.format( args );
1669 }
1670
1671 }