All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rexsl.page.JaxbGroup Maven / Gradle / Ivy

There is a newer version: 0.13.3
Show newest version
/**
 * Copyright (c) 2011-2013, ReXSL.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the ReXSL.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.rexsl.page;

import com.jcabi.log.Logger;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import lombok.ToString;

/**
 * JAXB group of elements.
 *
 * 

A convenient utility class that enables on-fly creation of JAXB annotated * collections of elements, for example: * *

 @XmlRootElement
 * public class Page {
 *   @XmlElement
 *   public Object getEmployees() {
 *     Collection<Employee> employees = this.retrieve();
 *     return JaxbGroup.build(employees, "employee");
 *   }
 * }
* *

It's even more convenient in combination with {@link JaxbBundle}, for * example: * *

 @XmlRootElement
 * public class Page {
 *   @XmlElement
 *   public Object getEmployee() {
 *     return new JaxbBundle("employee")
 *       .attr("age", "45")
 *       .add("depts", JaxbGroup.build(this.depts(), "dept"))
 *       .up()
 *       .element();
 *   }
 * }
* *

The class is immutable and thread-safe. * * @author Yegor Bugayenko ([email protected]) * @version $Id$ * @since 0.3.7 */ @XmlRootElement @XmlAccessorType(XmlAccessType.NONE) @ToString public final class JaxbGroup { /** * Classes already created before. */ private static final ConcurrentMap> READY = new ConcurrentHashMap>(0); /** * Collection of elements. */ @NotNull private final transient Collection group; /** * Public ctor, for JAXB (always throws a runtime exception). */ public JaxbGroup() { throw new IllegalStateException( // @checkstyle LineLength (1 line) "JaxbGroup can't be instantiated with default ctor, use JaxbGroup#build(..) instead" ); } /** * Protected ctor, for on-fly instantiating (you're not supposed to use it). * @param grp Group of elements */ protected JaxbGroup(final Collection grp) { this.group = grp; } /** * Creates a new JAXB-annotated collection of elements. * @param grp Group of elements (JAXB-annotated) * @param name Name of parent XML element * @return JAXB-annotated object, just created */ @NotNull public static Object build( @NotNull(message = "group can't be NULL") final Collection grp, @NotNull(message = "name can't be NULL") final String name) { synchronized (JaxbGroup.READY) { final String mnemo = JaxbGroup.mnemo(grp.isEmpty(), name); if (!JaxbGroup.READY.containsKey(mnemo)) { JaxbGroup.READY.put( mnemo, JaxbGroup.construct(JaxbGroup.types(grp), name) ); } try { return JaxbGroup.READY.get(mnemo) .getDeclaredConstructor(Collection.class) .newInstance(grp); } catch (NoSuchMethodException ex) { throw new IllegalStateException(ex); } catch (InstantiationException ex) { throw new IllegalStateException(ex); } catch (IllegalAccessException ex) { throw new IllegalStateException(ex); } catch (InvocationTargetException ex) { throw new IllegalStateException(ex); } } } /** * Get group of elements. * @return The collection */ @XmlAnyElement(lax = true) @XmlMixed @NotNull(message = "collection is never NULL") public Collection getGroup() { return Collections.unmodifiableCollection(this.group); } /** * Create NEW class name. * @param empty Is it an empty group? * @param name Name of root element * @return The name */ private static String mnemo(final boolean empty, final String name) { return String.format( "%s$%s$%s", JaxbGroup.class.getName(), name, // @checkstyle AvoidInlineConditionals (1 line) empty ? "empty" : "full" ); } /** * Construct new class. * @param types Types used in the collection * @param name Name of root element * @return Class just created */ private static Class construct(final Collection> types, final String name) { try { final CtClass ctc = PageBuilder.POOL.getAndRename( JaxbGroup.class.getName(), JaxbGroup.mnemo(types.isEmpty(), name) ); final ClassFile file = ctc.getClassFile(); final AnnotationsAttribute attribute = (AnnotationsAttribute) file.getAttribute( AnnotationsAttribute.visibleTag ); attribute.addAnnotation(JaxbGroup.xmlRootElement(file, name)); if (!types.isEmpty()) { attribute.addAnnotation(JaxbGroup.xmlSeeAlso(file, types)); } final Class cls = ctc.toClass(); ctc.defrost(); Logger.debug( JaxbGroup.class, "#construct('%s'): class %s constructed", name, cls.getName() ); return cls; } catch (NotFoundException ex) { throw new IllegalStateException(ex); } catch (CannotCompileException ex) { throw new IllegalStateException(ex); } } /** * Find all types used in the collection. * @param grp The collection * @return List of types used there */ private static Collection> types(final Collection grp) { final Collection> types = new HashSet>(grp.size()); for (final Object element : grp) { types.add(element.getClass()); } return types; } /** * Create new XmlRootElement annotation. * @param file Javassist file to work with * @param name Name of root element * @return The annotation */ private static Annotation xmlRootElement(final ClassFile file, final String name) { final AnnotationsAttribute attribute = AnnotationsAttribute.class.cast( file.getAttribute(AnnotationsAttribute.visibleTag) ); final Annotation annotation = attribute.getAnnotation( XmlRootElement.class.getName() ); annotation.addMemberValue( "name", new StringMemberValue(name, file.getConstPool()) ); Logger.debug( JaxbGroup.class, "#xmlRootElement(.., '%s'): annotation created", name ); return annotation; } /** * Create new XmlSeeAlso annotation. * @param file Javassist file to work with * @param types The class to refer to * @return The annotation */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") private static Annotation xmlSeeAlso(final ClassFile file, final Collection> types) { final Annotation annotation = new Annotation( XmlSeeAlso.class.getName(), file.getConstPool() ); final ArrayMemberValue member = new ArrayMemberValue( file.getConstPool() ); final ClassMemberValue[] values = new ClassMemberValue[types.size()]; int pos = 0; for (final Class type : types) { values[pos] = new ClassMemberValue( type.getName(), file.getConstPool() ); pos += 1; } member.setValue(values); annotation.addMemberValue("value", member); Logger.debug( JaxbGroup.class, "#xmlSeeAlso(.., %d classes): annotation created", types.size() ); return annotation; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy