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