http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayCache.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayCache.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayCache.java new file mode 100644 index 0000000..9747902 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayCache.java @@ -0,0 +1,106 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.io.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * A utility class for caching byte arrays in memory so that duplicate arrays can be reused. + * <p> + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public class ByteArrayCache { + + /** + * Default global byte array cache. + * Note that this can't ever get garbage collected so don't add really large arrays! + */ + public static final ByteArrayCache DEFAULT = new ByteArrayCache(); + + private final ConcurrentHashMap<ByteArray,byte[]> cache = new ConcurrentHashMap<ByteArray,byte[]>(); + + /** + * Add the specified byte array to this cache. + * + * @param contents The byte array to add to this cache. + * @return Either the same byte array or a previously cached byte array depending on whether the byte array + * already exists in the cache. + */ + public byte[] cache(byte[] contents) { + if (contents == null) + return null; + ByteArray ba = new ByteArray(contents); + cache.putIfAbsent(ba, ba.contents); + return cache.get(ba); + } + + /** + * Add the specified input stream to this cache. + * + * @param contents The input stream whose contents are to be added to this cache. + * @return Either the same byte array or a previously cached byte array depending on whether the byte array + * already exists in the cache. + * @throws IOException + */ + public byte[] cache(InputStream contents) throws IOException { + if (contents == null) + return null; + ByteArray ba = new ByteArray(IOUtils.readBytes(contents, 1024)); + cache.putIfAbsent(ba, ba.contents); + return cache.get(ba); + } + + /** + * Returns the number of byte arrays in this cache. + * + * @return The number of byte arrays in this cache. + */ + public int size() { + return cache.size(); + } + + private static class ByteArray { + private int hashCode; + private byte[] contents; + + private ByteArray(byte[] contents) { + this.contents = contents; + int multiplier = 1; + for (int i = 0; i < contents.length; i++) { + hashCode += contents[i] * multiplier; + int shifted = multiplier << 5; + multiplier = shifted - multiplier; + } + } + + @Override /* Object */ + public int hashCode() { + if (hashCode == 0) { + } + return hashCode; + } + + @Override /* Object */ + public boolean equals(Object o) { + if (o instanceof ByteArray) { + ByteArray ba = (ByteArray)o; + if (ba.hashCode == hashCode) + return Arrays.equals(ba.contents, contents); + } + return false; + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayInOutStream.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayInOutStream.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayInOutStream.java new file mode 100644 index 0000000..d104c77 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ByteArrayInOutStream.java @@ -0,0 +1,32 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.io.*; + +/** + * Subclass of a ByteArrayOutputStream that avoids a byte array copy when reading from an input stream. + * <p> + * @author James Bognar (james.bog...@salesforce.com) + */ +public class ByteArrayInOutStream extends ByteArrayOutputStream { + + /** + * Creates a new input stream from this object. + * + * @return A new input stream from this object. + */ + public ByteArrayInputStream getInputStream() { + return new ByteArrayInputStream(this.buf, 0, this.count); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/CharSequenceReader.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/CharSequenceReader.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/CharSequenceReader.java new file mode 100644 index 0000000..fe89635 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/CharSequenceReader.java @@ -0,0 +1,100 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.io.*; + +/** + * Similar to {@link StringReader} except reads from a generic {@link CharSequenceReader}. + * + * @author jbognar + */ +public final class CharSequenceReader extends BufferedReader { + + private final CharSequence cs; + private String s; + private StringBuffer sb; + private StringBuilder sb2; + private int length; + private int next = 0; + + /** + * Constructor. + * + * @param cs The char sequence to read from. Can be <jk>null</jk>. + */ + public CharSequenceReader(CharSequence cs) { + super(new StringReader(""), 1); // Does not actually use a reader. + if (cs == null) + cs = ""; + this.cs = cs; + if (cs instanceof String) + s = (String)cs; + else if (cs instanceof StringBuffer) + sb = (StringBuffer)cs; + else if (cs instanceof StringBuilder) + sb2 = (StringBuilder)cs; + this.length = cs.length(); + } + + @Override /* Reader */ + public int read() { + if (next >= length) + return -1; + return cs.charAt(next++); + } + + @Override /* Reader */ + public boolean markSupported() { + return false; + } + + @Override /* Reader */ + public int read(final char[] cbuf, final int off, final int len) { + if (next >= length) + return -1; + int n = Math.min(length - next, len); + if (s != null) + s.getChars(next, next + n, cbuf, off); + else if (sb != null) + sb.getChars(next, next + n, cbuf, off); + else if (sb2 != null) + sb2.getChars(next, next + n, cbuf, off); + else { + for (int i = 0; i < n; i++) + cbuf[off+i] = cs.charAt(next+i); + } + next += n; + return n; + } + + @Override /* Reader */ + public long skip(long ns) { + if (next >= length) + return 0; + long n = Math.min(length - next, ns); + n = Math.max(-next, n); + next += n; + return n; + } + + @Override /* Reader */ + public void close() { + // no-op + } + + @Override /* Object */ + public String toString() { + return cs.toString(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/ClassUtils.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/ClassUtils.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ClassUtils.java new file mode 100644 index 0000000..fce5caf --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ClassUtils.java @@ -0,0 +1,323 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; + +/** + * Class-related utility methods. + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public final class ClassUtils { + + /** + * Given the specified list of objects, return readable names for the class types of the objects. + * + * @param o The objects. + * @return An array of readable class type strings. + */ + public static ObjectList getReadableClassNames(Object[] o) { + ObjectList l = new ObjectList(); + for (int i = 0; i < o.length; i++) + l.add(o[i] == null ? "null" : getReadableClassName(o[i].getClass())); + return l; + } + + /** + * Shortcut for calling <code><jsm>getReadableClassName</jsm>(c.getName())</code> + * + * @param c The class. + * @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. + */ + public static String getReadableClassName(Class<?> c) { + if (c == null) + return null; + return getReadableClassName(c.getName()); + } + + /** + * Shortcut for calling <code><jsm>getReadableClassName</jsm>(c.getClass().getName())</code> + * + * @param o The object whose class we want to render. + * @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. + */ + public static String getReadableClassNameForObject(Object o) { + if (o == null) + return null; + return getReadableClassName(o.getClass().getName()); + } + + /** + * Converts the specified class name to a readable form when class name is a special construct like <js>"[[Z"</js>. + * <p> + * Examples: + * <p class='bcode'> + * <jsm>getReadableClassName</jsm>(<js>"java.lang.Object"</js>); <jc>// Returns "java.lang.Object"</jc> + * <jsm>getReadableClassName</jsm>(<js>"boolean"</js>); <jc>// Returns "boolean"</jc> + * <jsm>getReadableClassName</jsm>(<js>"[Z"</js>); <jc>// Returns "boolean[]"</jc> + * <jsm>getReadableClassName</jsm>(<js>"[[Z"</js>); <jc>// Returns "boolean[][]"</jc> + * <jsm>getReadableClassName</jsm>(<js>"[Ljava.lang.Object;"</js>); <jc>// Returns "java.lang.Object[]"</jc> + * <jsm>getReadableClassName</jsm>(<jk>null</jk>); <jc>// Returns null</jc> + * </p> + * + * @param className The class name. + * @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. + */ + public static String getReadableClassName(String className) { + if (className == null) + return null; + if (! StringUtils.startsWith(className, '[')) + return className; + int depth = 0; + for (int i = 0; i < className.length(); i++) { + if (className.charAt(i) == '[') + depth++; + else + break; + } + char type = className.charAt(depth); + String c; + switch (type) { + case 'Z': c = "boolean"; break; + case 'B': c = "byte"; break; + case 'C': c = "char"; break; + case 'D': c = "double"; break; + case 'F': c = "float"; break; + case 'I': c = "int"; break; + case 'J': c = "long"; break; + case 'S': c = "short"; break; + default: c = className.substring(depth+1, className.length()-1); + } + StringBuilder sb = new StringBuilder(c.length() + 2*depth).append(c); + for (int i = 0; i < depth; i++) + sb.append("[]"); + return sb.toString(); + } + + /** + * Converts the string generated by {@link #getReadableClassName(Class)} back into a {@link Class}. + * <p> + * Generics are stripped from the string since they cannot be converted to a class. + * + * @param cl The classloader to use to load the class. + * @param name The readable class name. + * @return The class object. + * @throws ClassNotFoundException + */ + public static Class<?> getClassFromReadableName(ClassLoader cl, String name) throws ClassNotFoundException { + return cl.loadClass(name); + } + + /** + * Returns <jk>true</jk> if <code>parent</code> is a parent class of <code>child</code>. + * + * @param parent The parent class. + * @param child The child class. + * @param strict If <jk>true</jk> returns <jk>false</jk> if the classes are the same. + * @return <jk>true</jk> if <code>parent</code> is a parent class of <code>child</code>. + */ + public static boolean isParentClass(Class<?> parent, Class<?> child, boolean strict) { + return parent.isAssignableFrom(child) && ((!strict) || ! parent.equals(child)); + } + + /** + * Returns <jk>true</jk> if <code>parent</code> is a parent class or the same as <code>child</code>. + * + * @param parent The parent class. + * @param child The child class. + * @return <jk>true</jk> if <code>parent</code> is a parent class or the same as <code>child</code>. + */ + public static boolean isParentClass(Class<?> parent, Class<?> child) { + return isParentClass(parent, child, false); + } + + /** + * Comparator for use with {@link TreeMap TreeMaps} with {@link Class} keys. + * + * @author James Bognar (james.bog...@salesforce.com) + */ + public final static class ClassComparator implements Comparator<Class<?>>, Serializable { + + private static final long serialVersionUID = 1L; + + @Override /* Comparator */ + public int compare(Class<?> object1, Class<?> object2) { + return object1.getName().compareTo(object2.getName()); + } + } + + /** + * Returns the signature of the specified method. + * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>. + * For methods with one or more args, the arguments will be fully-qualified class names (e.g. <js>"append(java.util.StringBuilder,boolean)"</js>) + * + * @param m The methods to get the signature on. + * @return The methods signature. + */ + public static String getMethodSignature(Method m) { + StringBuilder sb = new StringBuilder(m.getName()); + Class<?>[] pt = m.getParameterTypes(); + if (pt.length > 0) { + sb.append('('); + for (int i = 0; i < pt.length; i++) { + if (i > 0) + sb.append(','); + sb.append(getReadableClassName(pt[i])); + } + sb.append(')'); + } + return sb.toString(); + } + + private final static Map<Class<?>, Class<?>> pmap1 = new HashMap<Class<?>, Class<?>>(), pmap2 = new HashMap<Class<?>, Class<?>>(); + static { + pmap1.put(boolean.class, Boolean.class); + pmap1.put(byte.class, Byte.class); + pmap1.put(short.class, Short.class); + pmap1.put(char.class, Character.class); + pmap1.put(int.class, Integer.class); + pmap1.put(long.class, Long.class); + pmap1.put(float.class, Float.class); + pmap1.put(double.class, Double.class); + pmap2.put(Boolean.class, boolean.class); + pmap2.put(Byte.class, byte.class); + pmap2.put(Short.class, short.class); + pmap2.put(Character.class, char.class); + pmap2.put(Integer.class, int.class); + pmap2.put(Long.class, long.class); + pmap2.put(Float.class, float.class); + pmap2.put(Double.class, double.class); + } + + /** + * If the specified class is a primitive (e.g. <code><jk>int</jk>.<jk>class</jk></code>) + * returns it's wrapper class (e.g. <code>Integer.<jk>class</jk></code>). + * + * @param c The class. + * @return The wrapper class, or <jk>null</jk> if class is not a primitive. + */ + public static Class<?> getPrimitiveWrapper(Class<?> c) { + return pmap1.get(c); + } + + /** + * If the specified class is a primitive wrapper (e.g. <code><jk>Integer</jk>.<jk>class</jk></code>) + * returns it's primitive class (e.g. <code>int.<jk>class</jk></code>). + * + * @param c The class. + * @return The primitive class, or <jk>null</jk> if class is not a primitive wrapper. + */ + public static Class<?> getPrimitiveForWrapper(Class<?> c) { + return pmap2.get(c); + } + + /** + * If the specified class is a primitive (e.g. <code><jk>int</jk>.<jk>class</jk></code>) + * returns it's wrapper class (e.g. <code>Integer.<jk>class</jk></code>). + * + * @param c The class. + * @return The wrapper class if it's primitive, or the same class if class is not a primitive. + */ + public static Class<?> getWrapperIfPrimitive(Class<?> c) { + if (! c.isPrimitive()) + return c; + return pmap1.get(c); + } + + /** + * Returns <jk>true</jk> if the specified class has the {@link Deprecated @Deprecated} annotation on it. + * + * @param c The class. + * @return <jk>true</jk> if the specified class has the {@link Deprecated @Deprecated} annotation on it. + */ + public static boolean isNotDeprecated(Class<?> c) { + return ! c.isAnnotationPresent(Deprecated.class); + } + + /** + * Returns <jk>true</jk> if the specified method has the {@link Deprecated @Deprecated} annotation on it. + * + * @param m The method. + * @return <jk>true</jk> if the specified method has the {@link Deprecated @Deprecated} annotation on it. + */ + public static boolean isNotDeprecated(Method m) { + return ! m.isAnnotationPresent(Deprecated.class); + + } + + /** + * Returns <jk>true</jk> if the specified constructor has the {@link Deprecated @Deprecated} annotation on it. + * + * @param c The constructor. + * @return <jk>true</jk> if the specified constructor has the {@link Deprecated @Deprecated} annotation on it. + */ + public static boolean isNotDeprecated(Constructor<?> c) { + return ! c.isAnnotationPresent(Deprecated.class); + } + + /** + * Returns <jk>true</jk> if the specified class is public. + * + * @param c The class. + * @return <jk>true</jk> if the specified class is public. + */ + public static boolean isPublic(Class<?> c) { + return Modifier.isPublic(c.getModifiers()); + } + + /** + * Returns <jk>true</jk> if the specified class is public. + * + * @param c The class. + * @return <jk>true</jk> if the specified class is public. + */ + public static boolean isStatic(Class<?> c) { + return Modifier.isStatic(c.getModifiers()); + } + + /** + * Returns <jk>true</jk> if the specified method is public. + * + * @param m The method. + * @return <jk>true</jk> if the specified method is public. + */ + public static boolean isPublic(Method m) { + return Modifier.isPublic(m.getModifiers()); + } + + /** + * Returns <jk>true</jk> if the specified method is static. + * + * @param m The method. + * @return <jk>true</jk> if the specified method is static. + */ + public static boolean isStatic(Method m) { + return Modifier.isStatic(m.getModifiers()); + } + + /** + * Returns <jk>true</jk> if the specified constructor is public. + * + * @param c The constructor. + * @return <jk>true</jk> if the specified constructor is public. + */ + public static boolean isPublic(Constructor<?> c) { + return Modifier.isPublic(c.getModifiers()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/CollectionUtils.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/CollectionUtils.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/CollectionUtils.java new file mode 100644 index 0000000..9c8ad8f --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/CollectionUtils.java @@ -0,0 +1,57 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.util.*; + +/** + * Utility methods for collections. + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public class CollectionUtils { + + /** + * Reverses the order of a {@link LinkedHashMap}. + * + * @param in The map to reverse the order on. + * @return A new {@link LinkedHashMap} with keys in reverse order. + */ + public static <K,V> LinkedHashMap<K,V> reverse(LinkedHashMap<K,V> in) { + if (in == null) + return null; + LinkedHashMap<K,V> m = new LinkedHashMap<K,V>(); + + // Note: Entry objects are reusable in an entry set, so we simply can't + // create a reversed iteration of that set. + List<K> keys = new ArrayList<K>(in.keySet()); + List<V> values = new ArrayList<V>(in.values()); + for (int i = in.size()-1; i >= 0; i--) + m.put(keys.get(i), values.get(i)); + + return m; + } + + /** + * Add a value to a list if the value is not null. + * + * @param l The list to add to. + * @param o The element to add. + * @return The same list. + */ + public static <T> List<T> addIfNotNull(List<T> l, T o) { + if (o != null) + l.add(o); + return l; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateBeanMap.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateBeanMap.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateBeanMap.java new file mode 100644 index 0000000..6110f2a --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateBeanMap.java @@ -0,0 +1,127 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.util.*; + +import org.apache.juneau.*; + +/** + * Represents a wrapped {@link BeanMap} where property values can be overridden, removed, or reordered + * without affecting the underlying bean. + * <p> + * Provides the {@link #filterKeys(List)} method for specifying the keys to keep in the bean map + * and in what order they should appear. + * + * @author James Bognar (james.bog...@salesforce.com) + * @param <T> The class type of the wrapped bean. + */ +@SuppressWarnings("hiding") +public class DelegateBeanMap<T> extends BeanMap<T> { + + private Set<String> keys = Collections.newSetFromMap(new LinkedHashMap<String,Boolean>()); + private ObjectMap overrideValues = new ObjectMap(); + + /** + * Constructor. + * + * @param bean The bean being wrapped. + * @param bc The bean context that created this bean map. + */ + @SuppressWarnings("unchecked") + public + DelegateBeanMap(T bean, BeanContext bc) { + super(bean, bc.getBeanMeta((Class<T>)bean.getClass())); + } + + /** + * Add a key in the next position. + * + * @param key The key to add. + */ + public void addKey(String key) { + this.keys.add(key); + } + + @Override /* Map */ + public Object put(String key, Object val) { + this.overrideValues.put(key, val); + this.keys.add(key); + return null; + } + + @Override /* Map */ + public Object get(Object key) { + if (overrideValues.containsKey(key)) + return overrideValues.get(key); + return super.get(key); + } + + @Override /* Map */ + public Set<String> keySet() { + return keys; + } + + /** + * Remove all but the specified properties from this bean map. + * <p> + * This does not affect the underlying bean. + * + * @param keys The remaining keys in the bean map (in the specified order). + */ + public void filterKeys(List<String> keys) { + this.keys.clear(); + this.keys.addAll(keys); + } + + @Override /* Map */ + public Object remove(Object key) { + keys.remove(key); + return null; + } + + @Override /* BeanMap */ + public BeanMeta<T> getMeta() { + return new BeanMetaFiltered<T>(super.getMeta(), keys); + } + + @Override /* Map */ + public Set<Entry<String,Object>> entrySet() { + Set<Entry<String,Object>> s = Collections.newSetFromMap(new LinkedHashMap<Map.Entry<String,Object>,Boolean>()); + for (final String key : keys) { + BeanMapEntry<T> bme; + if (overrideValues.containsKey(key)) + bme = new BeanMapEntryOverride<T>(this, this.getPropertyMeta(key), overrideValues.get(key)); + else + bme = this.getProperty(key); + if (bme == null) + throw new BeanRuntimeException(super.getClassMeta().getInnerClass(), "Property ''{0}'' not found on class.", key); + s.add(bme); + } + return s; + } + + private class BeanMapEntryOverride<T2> extends BeanMapEntry<T2> { + Object value; + + private BeanMapEntryOverride(BeanMap<T2> bm, BeanPropertyMeta<T2> bpm, Object value) { + super(bm, bpm); + this.value = value; + } + + @Override /* Map.Entry */ + public Object getValue() { + return value; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateList.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateList.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateList.java new file mode 100644 index 0000000..bde29c7 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateList.java @@ -0,0 +1,44 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.util.*; + +import org.apache.juneau.*; + +/** + * Represents a wrapped {@link Collection} where entries in the list can be removed or reordered without + * affecting the underlying list. + * + * @author James Bognar (james.bog...@salesforce.com) + * @param <T> The class type of the wrapped bean. + */ +public class DelegateList<T extends Collection<?>> extends ObjectList implements Delegate<T> { + private static final long serialVersionUID = 1L; + + private transient ClassMeta<T> classMeta; + + /** + * Constructor. + * + * @param classMeta The metadata object that created this delegate list. + */ + public DelegateList(ClassMeta<T> classMeta) { + this.classMeta = classMeta; + } + + @Override /* Delegate */ + public ClassMeta<T> getClassMeta() { + return classMeta; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateMap.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateMap.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateMap.java new file mode 100644 index 0000000..0b9a4f4 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/DelegateMap.java @@ -0,0 +1,59 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.util.*; + +import org.apache.juneau.*; + +/** + * Represents a wrapped {@link Map} where entries in the map can be removed without + * affecting the underlying map. + * + * @author James Bognar (james.bog...@salesforce.com) + * @param <T> The class type of the wrapped bean. + */ +public class DelegateMap<T> extends ObjectMap implements Delegate<T> { + private static final long serialVersionUID = 1L; + + private transient ClassMeta<T> classMeta; + + /** + * Constructor. + * + * @param classMeta The metadata object that created this delegate object. + */ + public DelegateMap(ClassMeta<T> classMeta) { + this.classMeta = classMeta; + } + + @Override /* Delegate */ + public ClassMeta<T> getClassMeta() { + return classMeta; + } + + /** + * Remove all but the specified keys from this map. + * <p> + * This does not affect the underlying map. + * + * @param keys The remaining keys in the map (in the specified order). + */ + public void filterKeys(List<String> keys) { + ObjectMap m2 = new ObjectMap(); + for (String k : keys) + m2.put(k, get(k)); + this.clear(); + this.putAll(m2); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/FileUtils.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/FileUtils.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/FileUtils.java new file mode 100644 index 0000000..c01ab20 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/FileUtils.java @@ -0,0 +1,134 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +import java.io.*; + +/** + * File utilities. + */ +public class FileUtils { + + /** + * Same as {@link File#mkdirs()} except throws a RuntimeExeption if directory could not be created. + * + * @param f The directory to create. Must not be <jk>null</jk>. + * @param clean If <jk>true</jk>, deletes the contents of the directory if it already exists. + * @return The same file. + * @throws RuntimeException if directory could not be created. + */ + public static File mkdirs(File f, boolean clean) { + assertFieldNotNull(f, "f"); + if (f.exists()) { + if (clean) { + if (! delete(f)) + throw new RuntimeException("Could not clean directory '"+f.getAbsolutePath()+"'"); + } else { + return f; + } + } + if (! f.mkdirs()) + throw new RuntimeException("Could not create directory '" + f.getAbsolutePath() + "'"); + return f; + } + + /** + * Same as {@link #mkdirs(String, boolean)} but uses String path. + * + * @param path The path of the directory to create. Must not be <jk>null</jk> + * @param clean If <jk>true</jk>, deletes the contents of the directory if it already exists. + * @return The directory. + */ + public static File mkdirs(String path, boolean clean) { + assertFieldNotNull(path, "path"); + return mkdirs(new File(path), clean); + } + + /** + * Recursively deletes a file or directory. + * + * @param f The file or directory to delete. + * @return <jk>true</jk> if file or directory was successfully deleted. + */ + public static boolean delete(File f) { + if (f == null) + return true; + if (f.isDirectory()) { + File[] cf = f.listFiles(); + if (cf != null) + for (File c : cf) + delete(c); + } + return f.delete(); + } + + /** + * Creates a file if it doesn't already exist using {@link File#createNewFile()}. + * Throws a {@link RuntimeException} if the file could not be created. + * + * @param f The file to create. + */ + public static void create(File f) { + if (f.exists()) + return; + try { + if (! f.createNewFile()) + throw new RuntimeException("Could not create file '"+f.getAbsolutePath()+"'"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Updates the modified timestamp on the specified file. + * Method ensures that the timestamp changes even if it's been modified within the past millisecond. + * + * @param f The file to modify the modified timestamp on. + */ + public static void modifyTimestamp(File f) { + long lm = f.lastModified(); + long l = System.currentTimeMillis(); + if (lm == l) + l++; + if (! f.setLastModified(l)) + throw new RuntimeException("Could not modify timestamp on file '"+f.getAbsolutePath()+"'"); + + // Linux only gives 1s precision, so set the date 1s into the future. + if (lm == f.lastModified()) { + l += 1000; + if (! f.setLastModified(l)) + throw new RuntimeException("Could not modify timestamp on file '"+f.getAbsolutePath()+"'"); + } + } + + /** + * Create a temporary file with the specified name. + * <p> + * The name is broken into file name and suffix, and the parts + * are passed to {@link File#createTempFile(String, String)}. + * <p> + * {@link File#deleteOnExit()} is called on the resulting file before being returned by this method. + * + * @param name The file name + * @return A newly-created temporary file. + * @throws IOException + */ + public static File createTempFile(String name) throws IOException { + String[] parts = name.split("\\."); + File f = File.createTempFile(parts[0], "." + parts[1]); + f.deleteOnExit(); + return f; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/FilteredMap.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/FilteredMap.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/FilteredMap.java new file mode 100644 index 0000000..b4ce73b --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/FilteredMap.java @@ -0,0 +1,96 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +import java.util.*; + +/** + * Wrapper around a map where the key names are overridden. + * + * @param <K> The key class type. + * @param <V> The value class type. + * @author James Bognar (james.bog...@salesforce.com) + */ +public final class FilteredMap<K,V> extends AbstractMap<K,V> { + + private Map<K,V> innerMap; + private Set<Map.Entry<K,V>> entries; + + /** + * Constructor. + * + * @param innerMap The map being wrapped. Must not be <jk>null</jk>. + * @param keys The keys in the new map. Must not be <jk>null</jk>. + */ + public FilteredMap(Map<K,V> innerMap, K[] keys) { + assertFieldNotNull(innerMap, "innerMap"); + assertFieldNotNull(keys, "keys"); + + this.innerMap = innerMap; + List<Map.Entry<K,V>> l = new ArrayList<Map.Entry<K,V>>(keys.length); + for (K k : keys) + if (innerMap.containsKey(k)) + l.add(createEntry(k)); + entries = new ListSet<Map.Entry<K,V>>(l); + } + + private Map.Entry<K,V> createEntry(final K key) { + return new Map.Entry<K,V>() { + + @Override /* Map.Entry */ + public K getKey() { + return key; + } + + @Override /* Map.Entry */ + public V getValue() { + return innerMap.get(key); + } + + @Override /* Map.Entry */ + public V setValue(V v) { + return innerMap.put(key, v); + } + }; + } + + + @Override /* Map */ + public Set<Map.Entry<K,V>> entrySet() { + return entries; + } + + /** + * A set with ordered entries (i.e. a List with a Set API). + */ + private static class ListSet<E> extends AbstractSet<E> { + + private List<E> entries; + + public ListSet(List<E> entries) { + this.entries = entries; + } + + @Override /* Set */ + public Iterator<E> iterator() { + return entries.iterator(); + } + + @Override /* Set */ + public int size() { + return entries.size(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/HashCode.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/HashCode.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/HashCode.java new file mode 100644 index 0000000..5bdfb80 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/HashCode.java @@ -0,0 +1,71 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +/** + * Utility class for generating integer hash codes. + * <p> + * General usage: + * <p class='bcode'> + * int hashCode = new HashCode().add("foobar").add(myobject).add(123).get(); + * </p> + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public final class HashCode { + + private int hashCode = 1; + + /** + * Create a new HashCode object. + * + * @return A new HashCode object. + */ + public static final HashCode create() { + return new HashCode(); + } + + + /** + * Hashes the hashcode of the specified object into this object. + * + * @param o The object whose hashcode will be hashed with this object. + * @return This object (for method chaining). + */ + public HashCode add(Object o) { + add(o == null ? 1 : o.hashCode()); + return this; + } + + /** + * Hashes the hashcode into this object. + * <p> + * The formula is simply <code>hashCode = 31*hashCode + i;</code> + * + * @param i The hashcode to hash into this object's hashcode. + * @return This object (for method chaining). + */ + public HashCode add(int i) { + hashCode = 31*hashCode + i; + return this; + } + + /** + * Return the calculated hashcode value. + * + * @return The calculated hashcode. + */ + public int get() { + return hashCode; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/IOUtils.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/IOUtils.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/IOUtils.java new file mode 100644 index 0000000..e37134c --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/IOUtils.java @@ -0,0 +1,349 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +import java.io.*; +import java.nio.charset.*; + +import org.apache.juneau.utils.*; + +/** + * Various I/O related utility methods. + * + * @author jbognar + */ +public final class IOUtils { + + /** UTF-8 charset */ + public static final Charset UTF8 = Charset.forName("UTF-8"); + + /** + * Reads the contents of a file into a string. + * + * @param path The path of the file to read using default character encoding. + * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public static String readFile(String path) throws IOException { + return read(new File(path)); + } + + /** + * Reads the contents of a file into a string. + * + * @param in The file to read using default character encoding. + * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public static String read(File in) throws IOException { + if (in == null || ! in.exists()) + return null; + Reader r = new InputStreamReader(new FileInputStream(in), Charset.defaultCharset()); + return read(r, 0, 1024); + } + + /** + * Writes the contents of the specified <code>Reader</code> to the specified file. + * + * @param out The file to write the output to. + * @param in The reader to pipe from. + * @return The number of characters written to the file. + * @throws IOException + */ + public static int write(File out, Reader in) throws IOException { + assertFieldNotNull(out, "out"); + assertFieldNotNull(in, "in"); + Writer w = new OutputStreamWriter(new FileOutputStream(out), Charset.defaultCharset()); + try { + return IOPipe.create(in, w).closeOut().run(); + } finally { + w.close(); + } + } + + /** + * Writes the contents of the specified <code>InputStream</code> to the specified file. + * + * @param out The file to write the output to. + * @param in The input stream to pipe from. + * @return The number of characters written to the file. + * @throws IOException + */ + public static int write(File out, InputStream in) throws IOException { + assertFieldNotNull(out, "out"); + assertFieldNotNull(in, "in"); + OutputStream os = new FileOutputStream(out); + try { + return IOPipe.create(in, os).closeOut().run(); + } finally { + os.close(); + } + } + + /** + * Reads the contents of a reader into a string. + * + * @param in The input reader. + * @return The contents of the reader as a string. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public static String read(Reader in) throws IOException { + return read(in, 0, 1024); + } + + /** + * Reads the contents of an input stream into a string using the specified charset. + * + * @param in The input stream. + * @param cs The charset of the contents of the input stream. + * @return The contents of the reader as a string. <jk>null</jk> if input stream was null. + * @throws IOException If a problem occurred trying to read from the input stream. + */ + public static String read(InputStream in, Charset cs) throws IOException { + if (in == null) + return null; + return read(new InputStreamReader(in, cs)); + } + + /** + * Reads the contents of an input stream into a string using the system default charset. + * + * @param in The input stream. + * @return The contents of the reader as a string, or <jk>null</jk> if the input stream is null. + * @throws IOException If a problem occurred trying to read from the input stream. + */ + public static String read(InputStream in) throws IOException { + if (in == null) + return null; + return read(new InputStreamReader(in, Charset.defaultCharset())); + } + + /** + * Read the specified input stream into a byte array and closes the stream. + * + * @param in The input stream. + * @param bufferSize The expected size of the buffer. + * @return The contents of the stream as a byte array. + * @throws IOException Thrown by underlying stream. + */ + public static byte[] readBytes(InputStream in, int bufferSize) throws IOException { + if (in == null) + return null; + ByteArrayOutputStream buff = new ByteArrayOutputStream(bufferSize); + int nRead; + byte[] b = new byte[Math.min(bufferSize, 8192)]; + + try { + while ((nRead = in.read(b, 0, b.length)) != -1) + buff.write(b, 0, nRead); + + buff.flush(); + + return buff.toByteArray(); + } finally { + in.close(); + } + } + + + /** + * Reads the specified input into a {@link String} until the end of the input is reached. + * <p> + * The {@code Reader} is automatically closed. + * <p> + * If the {@code Reader} is not an instance of a {@code BufferedReader}, then it gets wrapped in a {@code BufferedReader}. + * + * @param in The input reader. + * @param length Specify a positive number if the length of the input is known. + * @param bufferSize Specify the buffer size to use. + * @return The contents of the reader as a string. <jk>null</jk> if reader was null. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public static String read(Reader in, int length, int bufferSize) throws IOException { + if (in == null) + return null; + length = (length <= 0 ? bufferSize : length); + StringBuilder sb = new StringBuilder(length); // Assume they're ASCII characters. + try { + char[] buf = new char[Math.min(bufferSize, length)]; + int i = 0; + while ((i = in.read(buf)) != -1) + sb.append(buf, 0, i); + return sb.toString(); + } finally { + in.close(); + } + } + + /** + * Pipes the contents of the specified reader into the writer. + * The reader is closed, the writer is not. + * + * @param in The reader to pipe from. + * @param out The writer to pipe to. + * @throws IOException + */ + public static void pipe(Reader in, Writer out) throws IOException { + assertFieldNotNull(out, "out"); + assertFieldNotNull(in, "in"); + IOPipe.create(in, out).run(); + } + + /** + * Wraps the specified reader in a buffered reader. + * + * @param r The reader being wrapped. + * @return The reader wrapped in a {@link BufferedReader}, or the original {@link Reader} if it's already + * a buffered reader. + */ + public static Reader getBufferedReader(Reader r) { + if (r instanceof BufferedReader || r instanceof StringReader) + return r; + return new BufferedReader(r); + } + + /** + * Counts the number of bytes in the input stream and then closes the stream. + * + * @param is The input stream to read from. + * @return The number of bytes read. + * @throws IOException + */ + public static long count(InputStream is) throws IOException { + assertFieldNotNull(is, "is"); + long c = 0; + long i; + try { + while ((i = is.skip(1024)) != 0) + c += i; + } finally { + is.close(); + } + return c; + } + + /** + * Counts the number of characters in the reader and then closes the reader. + * + * @param r The reader to read from. + * @return The number of characters read. + * @throws IOException + */ + public static long count(Reader r) throws IOException { + assertFieldNotNull(r, "r"); + long c = 0; + long i; + try { + while ((i = r.skip(1024)) != 0) + c += i; + } finally { + r.close(); + } + return c; + } + + /** + * Given the specified <js>"Content-Length"</js> header value, return an appropriate buffer size. + * The maximum buffer size is 1MB. + * + * @param contentLength The value of the <js>"Content-Length"</js> header. + * @return The appropriate buffer size. + */ + public static int getBufferSize(String contentLength) { + try { + if (! StringUtils.isEmpty(contentLength)) { + long l = Long.decode(contentLength); + if (l > 1048576) + return 1048576; + if (l <= 0) + return 8192; + return (int)l; + } + } catch (Exception e) { + return 8192; + } + return 8192; + } + + /** + * Close input stream and ignore any exceptions. + * No-op if input stream is <jk>null</jk>. + * + * @param is The input stream to close. + */ + public static void closeQuietly(InputStream is) { + try { + if (is != null) + is.close(); + } catch (IOException e) {} + } + + /** + * Close output stream and ignore any exceptions. + * No-op if output stream is <jk>null</jk>. + * + * @param os The output stream to close. + */ + public static void closeQuietly(OutputStream os) { + try { + if (os != null) + os.close(); + } catch (IOException e) {} + } + + /** + * Close reader and ignore any exceptions. + * No-op if reader is <jk>null</jk>. + * + * @param r The reader to close. + */ + public static void closeQuietly(Reader r) { + try { + if (r != null) + r.close(); + } catch (IOException e) {} + } + + /** + * Close writer and ignore any exceptions. + * No-op if writer is <jk>null</jk>. + * + * @param w The writer to close. + */ + public static void closeQuietly(Writer w) { + try { + if (w != null) + w.close(); + } catch (IOException e) {} + } + + /** + * Quietly close all specified input streams, output streams, readers, and writers. + * + * @param o The list of all objects to quietly close. + */ + public static void closeQuietly(Object...o) { + for (Object o2 : o) { + if (o2 instanceof InputStream) + closeQuietly((InputStream)o2); + if (o2 instanceof OutputStream) + closeQuietly((OutputStream)o2); + if (o2 instanceof Reader) + closeQuietly((Reader)o2); + if (o2 instanceof Writer) + closeQuietly((Writer)o2); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/IdentityList.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/IdentityList.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/IdentityList.java new file mode 100644 index 0000000..2d9345d --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/IdentityList.java @@ -0,0 +1,49 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import java.util.*; + +/** + * Combination of a {@link LinkedList} and <code>IdentitySet</code>. + * <ul class='spaced-list'> + * <li>Duplicate objects (by identity) will be skipped during insertion. + * <li>Order of insertion maintained. + * </ul> + * <p> + * Note: This class is NOT thread safe, and is intended for use on small lists. + * + * @author James Bognar (james.bog...@salesforce.com) + * @param <T> Entry type. + */ +public class IdentityList<T> extends LinkedList<T> { + + private static final long serialVersionUID = 1L; + + @Override /* List */ + public boolean add(T t) { + for (T t2 : this) + if (t2 == t) + return false; + super.add(t); + return true; + } + + @Override /* List */ + public boolean contains(Object t) { + for (T t2 : this) + if (t2 == t) + return true; + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/JuneauLogger.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/JuneauLogger.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/JuneauLogger.java new file mode 100644 index 0000000..4bd90ef --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/JuneauLogger.java @@ -0,0 +1,295 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static java.util.logging.Level.*; + +import java.text.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; + +import org.apache.juneau.json.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.transforms.*; + +/** + * Wraps and extends the {@link java.util.logging.Logger} class to provide some additional convenience methods. + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public class JuneauLogger extends java.util.logging.Logger { + + private static final WriterSerializer serializer = JsonSerializer.DEFAULT_LAX.clone() + .addTransforms( + CalendarTransform.ISO8601DTZ.class, + DateTransform.ISO8601DTZ.class, + EnumerationTransform.class, + IteratorTransform.class + ); + + private static final ConcurrentHashMap<Class<?>,String> rbMap = new ConcurrentHashMap<Class<?>,String>(); + + private final ResourceBundle rb; + private final java.util.logging.Logger innerLogger; + + /** + * Get logger for specified class. + * + * @param forClass The class to create a logger for. + * @return A new <l>Logger</l>. + */ + public static JuneauLogger getLogger(Class<?> forClass) { + return new JuneauLogger(java.util.logging.Logger.getLogger(forClass.getName())); + } + + /** + * Get logger for specified class using the specified resource bundle name. + * + * @param forClass The class to create a logger for. + * @param resourceBundleName The name of the resource bundle. + * Can be any of the following formats: + * <ol> + * <li>An absolute path. E.g. <js>"com/ibm/nls/Messages"</js>. + * <li>A path relative to the package of the class. E.g. <js>"nls/Messages"</js>. + * </ol> + * Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters. + * @return A new <l>Logger</l>. + */ + public static JuneauLogger getLogger(Class<?> forClass, String resourceBundleName) { + return new JuneauLogger(java.util.logging.Logger.getLogger(forClass.getName(), resolveResourceBundleName(forClass, resourceBundleName))); + } + + /** + * Get logger with specified name using the specified resource bundle name. + * + * @param name The name of the logger to use. + * @param resourceBundleName The name of the resource bundle. + * Can be any of the following formats: + * <ol> + * <li>An absolute path. E.g. <js>"com/ibm/nls/Messages"</js>. + * <li>A path relative to the package of the class. E.g. <js>"nls/Messages"</js>. + * </ol> + * Both <js>'.'</js> and <js>'/'</js> can be used as path delimiters. + * @return A new <l>Logger</l>. + */ + public static JuneauLogger getLogger(String name, String resourceBundleName) { + return new JuneauLogger(java.util.logging.Logger.getLogger(name, resourceBundleName)); + } + + /** + * Constructor. + * + * @param innerLogger The wrapped logger. + */ + protected JuneauLogger(java.util.logging.Logger innerLogger) { + super(innerLogger.getName(), innerLogger.getResourceBundleName()); + this.innerLogger = innerLogger; + this.rb = getResourceBundle(); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void severe(String msg, Object...args) { + if (isLoggable(SEVERE)) + log(SEVERE, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void warning(String msg, Object...args) { + if (isLoggable(WARNING)) + log(WARNING, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void info(String msg, Object...args) { + if (isLoggable(INFO)) + log(INFO, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#CONFIG} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void config(String msg, Object...args) { + if (isLoggable(CONFIG)) + log(CONFIG, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINE} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void fine(String msg, Object...args) { + if (isLoggable(FINE)) + log(FINE, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINER} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void finer(String msg, Object...args) { + if (isLoggable(FINER)) + log(FINER, msg, args); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#FINEST} level. + * + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void finest(String msg, Object...args) { + if (isLoggable(FINEST)) + log(FINEST, msg, args); + } + + /** + * Logs an exception as {@link Level#SEVERE} level. + * + * @param t The Throwable object to log. + */ + public void severe(Throwable t) { + if (isLoggable(SEVERE)) + log(SEVERE, t.getLocalizedMessage(), t); + } + + /** + * Logs an exception as {@link Level#WARNING} level. + * + * @param t The Throwable object to log. + */ + public void warning(Throwable t) { + if (isLoggable(WARNING)) + log(WARNING, t.getLocalizedMessage(), t); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#SEVERE} level. + * + * @param t The Throwable object associated with the event that needs to be logged. + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void severe(Throwable t, String msg, Object...args) { + if (isLoggable(SEVERE)) + log(SEVERE, getMessage(msg, args), t); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#WARNING} level. + * + * @param t The Throwable object associated with the event that needs to be logged. + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void warning(Throwable t, String msg, Object...args) { + if (isLoggable(WARNING)) + log(WARNING, getMessage(msg, args), t); + } + + /** + * Logs a message with the specified {@link MessageFormat}-style arguments at {@link Level#INFO} level. + * + * @param t The Throwable object associated with the event that needs to be logged. + * @param msg The message to log. + * @param args The {@link MessageFormat}-style arguments. + */ + public void info(Throwable t, String msg, Object...args) { + if (isLoggable(INFO)) + log(INFO, getMessage(msg, args), t); + } + + @Override /* Logger */ + public void log(LogRecord record) { + innerLogger.log(record); + } + + @Override /* Logger */ + public boolean isLoggable(Level level) { + return innerLogger.isLoggable(level); + } + + /** + * Similar to {@link #log(Level, String, Object[])}, except arguments are converted to objects + * that are serialized using the {@link JsonSerializer#toStringObject(Object)} method. + * This allows arbitrary POJOs to be serialized as message parameters. + * + * @param level The level of the given message. + * @param msg The message to log. + * @param args The POJO arguments. + */ + public void logObjects(Level level, String msg, Object...args) { + if (isLoggable(level)) { + for (int i = 0; i < args.length; i++) + args[i] = serializer.toStringObject(args[i]); + log(level, msg, args); + } + } + + private String getMessage(String msg, Object...args) { + if (args.length == 0) + return msg; + if (rb != null && rb.containsKey(msg)) + msg = rb.getString(msg); + return MessageFormat.format(msg, args); + } + + private static String resolveResourceBundleName(Class<?> forClass, String path) { + if (StringUtils.isEmpty(path)) + return null; + String rb = rbMap.get(forClass); + if (rb == null) { + path = path.replace('/', '.'); + if (path.startsWith(".")) + path = path.substring(1); + ClassLoader cl = forClass.getClassLoader(); + try { + ResourceBundle.getBundle(path, Locale.getDefault(), cl); + rbMap.putIfAbsent(forClass, path); + } catch (MissingResourceException e) { + try { + path = forClass.getPackage().getName() + '.' + path; + ResourceBundle.getBundle(path, Locale.getDefault(), cl); + rbMap.putIfAbsent(forClass, path); + } catch (MissingResourceException e2) { + rbMap.putIfAbsent(forClass, ""); + } + } + rb = rbMap.get(forClass); + } + return ("".equals(rb) ? null : rb); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/KeywordSet.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/KeywordSet.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/KeywordSet.java new file mode 100644 index 0000000..c23a912 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/KeywordSet.java @@ -0,0 +1,90 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +/** + * Stores a set of language keywords for quick lookup. + * <p> + * Keywords must be: + * <ul class='spaced-list'> + * <li>2 or more characters in length. + * <li>Lowercase ASCII. + * </ul> + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public final class KeywordSet { + final char[][][][] store; + + /** + * Constructor. + * + * @param keywords The list of keywords. + */ + public KeywordSet(String... keywords) { + this.store = new char[26][][][]; + + for (String keyword : keywords) { + if (keyword.length() < 2) + illegalArg("Invalid keyword '{0}' passed to KeywordStore.", keyword); + int c0 = keyword.charAt(0) - 'a'; + int c1 = keyword.charAt(1) - 'a'; + if (c0 < 0 || c0 > 25 || c1 < 0 || c1 > 25) + illegalArg("Invalid keyword '{0}' passed to KeywordStore.", keyword); + if (this.store[c0] == null) + this.store[c0] = new char[26][][]; + char[][][] x1 = this.store[c0]; + char[][] x2; + if (x1[c1] == null) + x2 = new char[1][]; + else { + x2 = new char[x1[c1].length+1][]; + System.arraycopy(x1[c1], 0, x2, 0, x1[c1].length); + } + x2[x2.length-1] = keyword.toCharArray(); + x1[c1] = x2; + } + } + + /** + * Returns <jk>true</jk> if the specified string exists in this store. + * + * @param s The string to check. + * @return <jk>true</jk> if the specified string exists in this store. + */ + public boolean contains(String s) { + if (s == null || s.length() < 2) + return false; + int c0 = s.charAt(0) - 'a', c1 = s.charAt(1) - 'a'; + if (c0 < 0 || c0 > 25 || c1 < 0 || c1 > 25) + return false; + char[][][] x1 = store[c0]; + if (x1 == null) + return false; + char[][] x2 = x1[c1]; + if (x2 == null) + return false; + for (int i = 0; i < x2.length; i++) { + char[] keyword = x2[i]; + if (keyword.length == s.length()) { + for (int j = 0; j < keyword.length; j++) + if (keyword[j] != s.charAt(j)) + return false; + return true; + } + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiIterable.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiIterable.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiIterable.java new file mode 100644 index 0000000..19f5078 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiIterable.java @@ -0,0 +1,78 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +import java.util.*; + +/** + * Utility class for defining an iterator over one or more iterables. + * @param <E> The element class type. + */ +public class MultiIterable<E> implements Iterable<E> { + + final List<Iterator<E>> iterators = new LinkedList<Iterator<E>>(); + + /** + * Constructor. + * + * @param iterators The list of iterators to iterate over. + */ + public MultiIterable(Iterator<E>...iterators) { + for (Iterator<E> i : iterators) + append(i); + } + + /** + * Appends the specified iterator to this list of iterators. + * + * @param iterator The iterator to append. + * @return This object (for method chaining). + */ + public MultiIterable<E> append(Iterator<E> iterator) { + assertFieldNotNull(iterator, "iterator"); + this.iterators.add(iterator); + return this; + } + + @Override /* Iterable */ + public Iterator<E> iterator() { + return new Iterator<E>() { + Iterator<Iterator<E>> i1 = iterators.iterator(); + Iterator<E> i2 = i1.hasNext() ? i1.next() : null; + + @Override /* Iterator */ + public boolean hasNext() { + while (i2 != null && ! i2.hasNext()) + i2 = (i1.hasNext() ? i1.next() : null); + return (i2 != null); + } + + @Override /* Iterator */ + public E next() { + hasNext(); + if (i2 == null) + throw new NoSuchElementException(); + return i2.next(); + } + + @Override /* Iterator */ + public void remove() { + if (i2 == null) + throw new NoSuchElementException(); + i2.remove(); + } + }; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiSet.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiSet.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiSet.java new file mode 100644 index 0000000..49b84f0 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/MultiSet.java @@ -0,0 +1,111 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.ThrowableUtils.*; + +import java.util.*; + +/** + * Encapsulates multiple collections so they can be iterated over as if they + * were all part of the same collection. + * + * @author James Bognar (james.bog...@salesforce.com) + * @param <E> The object type of this set. + */ +public class MultiSet<E> extends AbstractSet<E> { + + /** Inner collections. */ + private List<Collection<E>> l = new ArrayList<Collection<E>>(); + + /** + * Create a new Set that consists as a coalesced set of the specified collections. + * + * @param c Zero or more collections to add to this set. + */ + public MultiSet(Collection<E>...c) { + for (Collection<E> cc : c) + append(cc); + } + + /** + * Appends the specified collection to this set of collections. + * + * @param c The collection to append to this set of collections. + * @return This object (for method chaining). + */ + public MultiSet<E> append(Collection<E> c) { + assertFieldNotNull(c, "c"); + l.add(c); + return this; + } + + /** + * Iterates over all entries in all collections. + */ + @Override /* Set */ + public Iterator<E> iterator() { + return new Iterator<E>() { + int i = 0; + Iterator<E> i2 = (l.size() > 0 ? l.get(i++).iterator() : null); + + @Override /* Iterator */ + public boolean hasNext() { + if (i2 == null) + return false; + if (i2.hasNext()) + return true; + for (int j = i; j < l.size(); j++) + if (l.get(j).size() > 0) + return true; + return false; + } + + @Override /* Iterator */ + public E next() { + if (i2 == null) + throw new NoSuchElementException(); + while (! i2.hasNext()) { + if (i >= l.size()) + throw new NoSuchElementException(); + i2 = l.get(i++).iterator(); + } + return i2.next(); + } + + @Override /* Iterator */ + public void remove() { + if (i2 == null) + throw new NoSuchElementException(); + i2.remove(); + } + }; + } + + /** + * Enumerates over all entries in all collections. + * + * @return An enumeration wrapper around this set. + */ + public Enumeration<E> enumerator() { + return Collections.enumeration(this); + } + + @Override /* Set */ + public int size() { + int i = 0; + for (Collection<E> c : l) + i += c.size(); + return i; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau/src/main/java/org/apache/juneau/internal/ReflectionUtils.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau/src/main/java/org/apache/juneau/internal/ReflectionUtils.java b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ReflectionUtils.java new file mode 100644 index 0000000..0936d91 --- /dev/null +++ b/org.apache.juneau/src/main/java/org/apache/juneau/internal/ReflectionUtils.java @@ -0,0 +1,163 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.internal; + +import static org.apache.juneau.internal.CollectionUtils.*; + +import java.io.*; +import java.lang.annotation.*; +import java.util.*; + +/** + * Reflection utilities. + * + * @author James Bognar (james.bog...@salesforce.com) + */ +public final class ReflectionUtils { + + /** + * Similar to {@link Class#getAnnotation(Class)} except also searches annotations on interfaces. + * + * @param <T> The annotation class type. + * @param a The annotation class. + * @param c The annotated class. + * @return The annotation, or <jk>null</jk> if not found. + */ + public static <T extends Annotation> T getAnnotation(Class<T> a, Class<?> c) { + if (c == null) + return null; + + T t = getDeclaredAnnotation(a, c); + if (t != null) + return t; + + t = getAnnotation(a, c.getSuperclass()); + if (t != null) + return t; + + for (Class<?> c2 : c.getInterfaces()) { + t = getAnnotation(a, c2); + if (t != null) + return t; + } + return null; + } + + /** + * Returns the specified annotation only if it's been declared on the specified class. + * <p> + * More efficient than calling {@link Class#getAnnotation(Class)} since it doesn't + * recursively look for the class up the parent chain. + * + * @param <T> The annotation class type. + * @param a The annotation class. + * @param c The annotated class. + * @return The annotation, or <jk>null</jk> if not found. + */ + @SuppressWarnings("unchecked") + public static <T extends Annotation> T getDeclaredAnnotation(Class<T> a, Class<?> c) { + for (Annotation a2 : c.getDeclaredAnnotations()) + if (a2.annotationType() == a) + return (T)a2; + return null; + } + + /** + * Returns all instances of the specified annotation on the specified class. + * <p> + * Searches all superclasses and superinterfaces. + * <p> + * Results are ordered child-to-parent. + * + * @param <T> The annotation class type. + * @param a The annotation class type. + * @param c The class being searched. + * @return The found matches, or an empty array if annotation was not found. + */ + public static <T extends Annotation> List<T> findAnnotations(Class<T> a, Class<?> c) { + List<T> l = new LinkedList<T>(); + appendAnnotations(a, c, l); + return l; + } + + /** + * Sames as {@link #findAnnotations(Class, Class)} except returns the annotations as a map + * with the keys being the class on which the annotation was found. + * <p> + * Results are ordered child-to-parent. + * + * @param <T> The annotation class type. + * @param a The annotation class type. + * @param c The class being searched. + * @return The found matches, or an empty array if annotation was not found. + */ + public static <T extends Annotation> LinkedHashMap<Class<?>,T> findAnnotationsMap(Class<T> a, Class<?> c) { + LinkedHashMap<Class<?>,T> m = new LinkedHashMap<Class<?>,T>(); + findAnnotationsMap(a, c, m); + return m; + } + + private static <T extends Annotation> void findAnnotationsMap(Class<T> a, Class<?> c, Map<Class<?>,T> m) { + if (c == null) + return; + + T t = getDeclaredAnnotation(a, c); + if (t != null) + m.put(c, t); + + findAnnotationsMap(a, c.getSuperclass(), m); + + for (Class<?> c2 : c.getInterfaces()) + findAnnotationsMap(a, c2, m); + } + + /** + * Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified list. + * + * @param a The annotation. + * @param c The class. + * @param l The list of annotations. + */ + public static <T extends Annotation> void appendAnnotations(Class<T> a, Class<?> c, List<T> l) { + if (c == null) + return; + + addIfNotNull(l, getDeclaredAnnotation(a, c)); + + if (c.getPackage() != null) + addIfNotNull(l, c.getPackage().getAnnotation(a)); + + appendAnnotations(a, c.getSuperclass(), l); + + for (Class<?> c2 : c.getInterfaces()) + appendAnnotations(a, c2, l); + } + + /** + * Similar to {@link Class#getResourceAsStream(String)} except looks up the + * parent hierarchy for the existence of the specified resource. + * + * @param c The class to return the resource on. + * @param name The resource name. + * @return An input stream on the specified resource, or <jk>null</jk> if the resource could not be found. + */ + public static InputStream getResource(Class<?> c, String name) { + while (c != null) { + InputStream is = c.getResourceAsStream(name); + if (is != null) + return is; + c = c.getSuperclass(); + } + return null; + } +}