JAXBContextCache.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-3.5.0).
003  * Copyright (c) 2007-2016 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmx.at)
019  */
020 package org.jenetics.internal.util;
021 
022 import static java.util.Objects.requireNonNull;
023 
024 import java.lang.reflect.Field;
025 import java.util.HashSet;
026 import java.util.Set;
027 import java.util.stream.Stream;
028 
029 import javax.xml.bind.DataBindingException;
030 import javax.xml.bind.JAXBContext;
031 import javax.xml.bind.JAXBException;
032 
033 import org.jenetics.util.ISeq;
034 
035 /**
036  * Caches the JAXB classes and lets you add additional one. You can either add
037  * a <em>JAXB</em> class directly, or the package where you have put in a
038  * {@code JAXBRegistry} class:
039  *
040  * <pre>{@code
041  * // Class may be package private
042  * final class JAXBRegistry {
043  *     private JAXBRegistry() {require.noInstance();}
044  *
045  *     // Must contain static final field 'CLASSES'.
046  *     public static final ISeq<Class<?>> CLASSES = ISeq.of(
047  *         BitGene.Model.class,
048  *         EnumGene.Model.class,
049  *         CharacterGene.Model.class,
050  *         IntegerGene.Model.class,
051  *         LongGene.Model.class,
052  *         DoubleGene.Model.class
053  *     )
054  * }
055  * }</pre>
056  *
057  * @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
058  * @version 3.5
059  * @since 3.5
060  */
061 public final class JAXBContextCache {
062     private JAXBContextCache() {require.noInstance();}
063 
064     private static final Set<String> PACKAGES = new HashSet<>();
065 
066     private static final Set<Class<?>> CLASSES = new HashSet<>();
067     static {
068         addPackage("org.jenetics");
069         addPackage("org.jenetics.engine");
070         addPackage("org.jenetics.internal.util");
071     }
072 
073     private static JAXBContext _context;
074 
075     /**
076      * Return a {@code JAXBContext} with the currently registered classes. This
077      * method is <em>synchronized</em>.
078      *
079      * @return the {@code JAXBContext} with the currently registered classes
080      */
081     public static synchronized JAXBContext context() {
082         if (_context == null) {
083             try {
084                 _context = JAXBContext
085                     .newInstance(CLASSES.toArray(new Class<?>[CLASSES.size()]));
086             } catch (JAXBException e) {
087                 throw new DataBindingException(
088                     "Something went wrong while creating JAXBContext.", e
089                 );
090             }
091         }
092 
093         return _context;
094     }
095 
096     /**
097      * Return a {@code JAXBContext} with the currently registered classes plus
098      * the registered classes in the given packages. This method is
099      * <em>synchronized</em>.
100      *
101      * @param packages the additional packages of the return {@code JAXBContext}
102      * @return the {@code JAXBContext}
103      */
104     public static JAXBContext context(final String... packages) {
105         Stream.of(packages).forEach(JAXBContextCache::addPackage);
106         return context();
107     }
108 
109     /**
110      * Return a {@code JAXBContext} with the currently registered classes plus
111      * the given classes. This method is <em>synchronized</em>.
112      *
113      * @param classes the additional classes of the return {@code JAXBContext}
114      * @return the {@code JAXBContext}
115      */
116     public static JAXBContext context(final Class<?>... classes) {
117         Stream.of(classes).forEach(JAXBContextCache::add);
118         return context();
119     }
120 
121     /**
122      * Register the given source package.
123      *
124      * @param pkg the package to register
125      */
126     public static synchronized void addPackage(final String pkg) {
127         if (!PACKAGES.contains(pkg)) {
128             PACKAGES.add(pkg);
129 
130             final ISeq<Class<?>> classes = jaxbClasses(pkg).stream()
131                 .filter(cls -> !CLASSES.contains(cls))
132                 .collect(ISeq.toISeq());
133 
134             if (!classes.isEmpty()) {
135                 _context = null;
136                 CLASSES.addAll(classes.asList());
137             }
138         }
139     }
140 
141     /**
142      * Register the given class.
143      *
144      * @param cls the class to register
145      */
146     public static synchronized void add(final Class<?> cls) {
147         requireNonNull(cls);
148 
149         if (!CLASSES.contains(cls)) {
150             _context = null;
151             CLASSES.add(cls);
152         }
153     }
154 
155     /**
156      * De-register the given class.
157      *
158      * @param cls the class to de-register
159      */
160     public static synchronized void remove(final Class<?> cls) {
161         requireNonNull(cls);
162 
163         if (CLASSES.contains(cls)) {
164             _context = null;
165             CLASSES.remove(cls);
166         }
167     }
168 
169     /**
170      * Check is the given class is already registered.
171      *
172      * @param cls the class to check
173      * @return {@code true} if the given class is already registered,
174      *         {@code false} otherwise.
175      */
176     public static synchronized boolean contains(final Class<?> cls) {
177         requireNonNull(cls);
178         return CLASSES.contains(cls);
179     }
180 
181     @SuppressWarnings("unchecked")
182     private static ISeq<Class<?>> jaxbClasses(final String pkg) {
183         requireNonNull(pkg);
184 
185         try {
186             final Field field = Class
187                 .forName(pkg + ".JAXBRegistry")
188                 .getField("CLASSES");
189             field.setAccessible(true);
190 
191             return (ISeq<Class<?>>)field.get(null);
192         } catch (ReflectiveOperationException e) {
193             return ISeq.empty();
194         }
195     }
196 }