| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed 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.xmlbeans.impl.jam.annotation; |
| |
| import org.apache.xmlbeans.impl.jam.provider.JamLogger; |
| import org.apache.xmlbeans.impl.jam.provider.JamServiceContext; |
| import org.apache.xmlbeans.impl.jam.JAnnotationValue; |
| import org.apache.xmlbeans.impl.jam.JClass; |
| import org.apache.xmlbeans.impl.jam.internal.elements.ElementContext; |
| |
| import java.util.Properties; |
| import java.util.Enumeration; |
| |
| /** |
| * <p>Provides a proxied view of some annotation artifact. JAM calls the |
| * public methods on this class to initialize the proxy with annotation |
| * values; those methods should not be called by user code.</p> |
| * |
| * <p>This class provides default implementations of |
| * initFromAnnotationInstance() and initFromJavadocTag() which will often be |
| * sufficient. However, extending classes are free to override them if |
| * they need to specialize how tags and annotations are mapped into the |
| * proxy's member values. A typical example might be overriding |
| * <code>initFromJavadocTag()</code> in provide a specialized parsing of |
| * the tags's name-value pairs.</p> |
| * |
| * @author Patrick Calahan <email: pcal-at-bea-dot-com> |
| */ |
| public abstract class AnnotationProxy { |
| |
| // ======================================================================== |
| // Constants |
| |
| /** |
| * <p>Name of the member of annotations which have only a single member. |
| * As specified in JSR175, that name is "value", but you should use |
| * this constant to prevent typos.</p> |
| */ |
| public static final String SINGLE_MEMBER_NAME = "value"; |
| |
| |
| /** |
| * <p>The delimiters to use by default when parsing out name=value pairs |
| * from a javadoc tag.</p> |
| */ |
| private static final String DEFAULT_NVPAIR_DELIMS = "\n\r"; |
| |
| private static final String VALUE_QUOTE = "\""; |
| |
| |
| // ======================================================================== |
| // Variables |
| |
| private JamServiceContext mContext; |
| |
| //FIXME need to expose a knob for setting this |
| private String mNvPairDelims = DEFAULT_NVPAIR_DELIMS; |
| |
| // ======================================================================== |
| // Initialization methods - called by JAM, don' |
| |
| /** |
| * <p>Called by JAM to initialize the proxy. Do not try to call this |
| * yourself.</p> |
| */ |
| public void init(JamServiceContext ctx) { |
| if (ctx == null) throw new IllegalArgumentException("null logger"); |
| mContext = ctx; |
| } |
| |
| // ======================================================================== |
| // Public abstract methods |
| |
| /** |
| * <p>Called by JAM to initialize a named member on this annotation proxy. |
| * </p> |
| */ |
| public abstract void setValue(String name, Object value, JClass type); |
| |
| //docme |
| public abstract JAnnotationValue[] getValues(); |
| |
| |
| //docme |
| public JAnnotationValue getValue(String named) { |
| if (named == null) throw new IllegalArgumentException("null name"); |
| //FIXME this impl is very gross |
| named = named.trim(); |
| JAnnotationValue[] values = getValues(); |
| for(int i=0; i<values.length; i++) { |
| |
| if (named.equals(values[i].getName())) return values[i]; |
| } |
| return null; |
| } |
| |
| // ======================================================================== |
| // Public methods |
| |
| //REVIEW i'm not sure this is sufficient. they might need access to |
| //the whole tag (including the name) when doing this. they may |
| //want to tell us what the tag name is. a bit of a chicken-and-egg |
| //problem. i guess if the need to do that, they just have to write |
| //their own CommentInitializer. |
| |
| /** |
| * <p>Called by JAM to initialize this proxy's properties using a |
| * javadoc tag. The parameter will contain the raw contents of the tag, |
| * excluding the name declaration (i.e. everything after the '@mytag').</p> |
| * |
| * <p>The implementation of this method parses the tagContents |
| * for 'name = value' pairs delimited by line breaks. If one or more such |
| * pairs is found, <code>setMemberValue</code> is called for each. If no |
| * such pairs are found, <code>setMemberValue()</code> is called using |
| * SINGLE_MEMBER_NAME as the name and the tag contents as the value.</p> |
| * |
| * <p>Extending classes are free to override this method if different |
| * behavior is required.</p> |
| */ |
| public void initFromJavadocTag(String tagline) { |
| //FIXME AND REVIEW so we're saying that tag values are always strings, |
| //at least by default. not clear that we can do any better than that. |
| //even if so, the way this is implemented here with this cast |
| //is somewhat gross |
| JClass type = |
| ((ElementContext)mContext).getClassLoader().loadClass("java.lang.String"); |
| if (tagline == null) throw new IllegalArgumentException("null tagline"); |
| tagline = tagline.trim(); |
| if (tagline.length() == 0) return; |
| Properties props = new Properties(); |
| parseAssignments(props,tagline); |
| if (props.size() == 0) { |
| setValue(SINGLE_MEMBER_NAME,tagline, type); |
| } else { |
| Enumeration names = props.propertyNames(); |
| |
| while(names.hasMoreElements()) { |
| String name = (String)names.nextElement(); |
| setValue(name,props.getProperty(name), type); |
| } |
| } |
| } |
| |
| // ======================================================================== |
| // Protected methods |
| |
| /** |
| * <p>Returns an instance of JamLogger that this AnnotationProxy should use |
| * for logging debug and error messages.</p> |
| */ |
| protected JamLogger getLogger() { return mContext.getLogger(); } |
| |
| |
| // ======================================================================== |
| // Private methods |
| |
| //REVIEW the comment parsing logic here should be factored and made pluggable |
| |
| /** |
| * Parse a line that contains assignments, taking into account |
| * - newlines (ignore them) |
| * - double quotes (the value is everything in-between) |
| * - // (everything after is ignored) |
| * - multiple assignments on the same line |
| * |
| * @param out This variable will contain a list of properties |
| * representing the line once parsed. |
| * @param line The line to be parsed |
| * |
| * This method contributed by Cedric Beust |
| */ |
| private void parseAssignments(Properties out, String line) { |
| getLogger().verbose("PARSING LINE " + line,this); |
| String originalLine = line; |
| line = removeComments(line); |
| while (null != line && -1 != line.indexOf("=")) { |
| int keyStart = -1; |
| int keyEnd = -1; |
| int ind = 0; |
| // Skip stuff before the key |
| char c = line.charAt(ind); |
| while (isBlank(c)) { |
| ind++; |
| c = line.charAt(ind); |
| } |
| keyStart = ind; |
| while (isLegal(line.charAt(ind))) ind++; |
| keyEnd = ind; |
| String key = line.substring(keyStart, keyEnd); |
| ind = line.indexOf("="); |
| if (ind == -1) { |
| return; //FIXME let's be a little conservative, just for now |
| //throw new IllegalStateException("'=' expected: "+line); |
| } |
| ind++; |
| // Skip stuff after the equal sign |
| try { |
| c = line.charAt(ind); |
| } |
| catch(StringIndexOutOfBoundsException ex){ |
| ex.printStackTrace(); |
| } |
| while (isBlank(c)) { |
| ind++; |
| c = line.charAt(ind); |
| } |
| |
| String value; |
| int valueStart = -1; |
| int valueEnd = -1; |
| if (c == '"') { |
| valueStart = ++ind; |
| while ('"' != line.charAt(ind)) { |
| ind++; |
| if (ind >= line.length()) { |
| getLogger().verbose("missing double quotes on line "+line,this); |
| } |
| } |
| valueEnd = ind; |
| } |
| else { |
| valueStart = ind++; |
| while (ind < line.length() && isLegal(line.charAt(ind))) ind++; |
| valueEnd = ind; |
| } |
| value = line.substring(valueStart, valueEnd); |
| if (ind < line.length()) { |
| line = line.substring(ind + 1); |
| } |
| else { |
| line = null; |
| } |
| getLogger().verbose("SETTING KEY:"+key+" VALUE:"+value,this); |
| out.setProperty(key, value); |
| } |
| } |
| |
| /** |
| * Remove all the texts between "//" and '\n' |
| * |
| * This method contributed by Cedric Beust |
| */ |
| private String removeComments(String value) { |
| String result = new String(); |
| int size = value.length(); |
| String current = value; |
| |
| int currentIndex = 0; |
| |
| int beginning = current.indexOf("//"); |
| |
| // |
| // Ignore // if it's between double quotes |
| // |
| int doubleQuotesIndex = current.indexOf("\""); |
| if (-1 != doubleQuotesIndex && doubleQuotesIndex < beginning) { |
| // do nothing |
| result = value; |
| } |
| else { |
| while (currentIndex < size && beginning != -1) { |
| beginning = value.indexOf("//", currentIndex); |
| if (-1 != beginning) { |
| if (beginning > 0 && value.charAt(beginning-1) == ':') { |
| //this is a quick fix for problem of unquoted url values. for |
| //now, just say it's not a comment if preceded by ':'. should |
| //review this later |
| currentIndex = beginning+2; |
| continue; |
| } |
| int end = value.indexOf('\n', beginning); |
| if (-1 == end) end = size; |
| // We have identified a portion to remove, copy the one we want to |
| // keep |
| result = result + value.substring(currentIndex, beginning).trim() + "\n"; |
| current = value.substring(end); |
| currentIndex = end; |
| } |
| } |
| result += current; |
| } |
| |
| return result.trim(); |
| } |
| |
| private boolean isBlank(char c) { |
| return c == ' ' || c == '\t' || c == '\n'; |
| } |
| |
| private boolean isLegal(char c) { |
| return (! isBlank(c)) && c != '='; |
| // return Character.isJavaIdentifierStart(c) || c == '-' || Character.isDigit(c) || c == '.'; |
| } |
| |
| /* |
| OLD TAG PARSING CODE |
| |
| public void initFromJavadocTag(String tagline) { |
| if (tagline == null) throw new IllegalArgumentException("null tagline"); |
| StringTokenizer st = new StringTokenizer(tagline, mNvPairDelims); |
| while (st.hasMoreTokens()) { |
| String pair = st.nextToken(); |
| int eq = pair.indexOf('='); |
| if (eq <= 0) continue; // if not there or is first character |
| String name = pair.substring(0, eq).trim(); |
| if (eq < pair.length() - 1) { |
| String value = pair.substring(eq + 1).trim(); |
| if (value.startsWith(VALUE_QUOTE)) { |
| value = parseQuotedValue(value.substring(1),st); |
| } |
| setValue(name,value); |
| } |
| } |
| } |
| |
| private String parseQuotedValue(String line, StringTokenizer st) { |
| StringWriter out = new StringWriter(); |
| while(true) { |
| int endQuote = line.indexOf(VALUE_QUOTE); |
| if (endQuote == -1) { |
| out.write(line); |
| if (!st.hasMoreTokens()) return out.toString(); |
| out.write('\n'); |
| line = st.nextToken().trim(); |
| continue; |
| } else { |
| out.write(line.substring(0,endQuote).trim()); |
| return out.toString(); |
| } |
| } |
| } |
| */ |
| |
| /** |
| * <p>Called by JAM to initialize this proxy's properties using a |
| * JSR175 annotation instnce. The value is guaranteed to be an instance |
| * of the 1.5-specific <code>java.lang.annotation.Annotation</code> |
| * marker interface. (It's typed as <code>Object</code> in order to |
| * preserve pre-1.5 compatibility).</p> |
| * |
| * <p>The implementation of this method introspects the given object |
| * for JSR175 annotation member methods, invokes them, and then calls |
| * <code>setMemberValue</code> using the method's name and invocation |
| * result as the name and value.</p> |
| * |
| * <p>Extending classes are free to override this method if different |
| * behavior is required.</p> |
| |
| public void initFromAnnotationInstance(Class annType, |
| Object jsr175annotationObject) { |
| if (jsr175annotationObject == null) throw new IllegalArgumentException(); |
| //FIXME this is a bit clumsy right now - I think we need to be a little |
| // more surgical in identifying the annotation member methods |
| Method[] methods = annType.getMethods(); |
| for(int i=0; i<methods.length; i++) { |
| int mods = methods[i].getModifiers(); |
| if (Modifier.isStatic(mods)) continue; |
| if (!Modifier.isPublic(mods)) continue; |
| if (methods[i].getParameterTypes().length > 0) continue; |
| { |
| // try to limit it to real annotation methods. |
| // FIXME seems like this could be better |
| Class c = methods[i].getDeclaringClass(); |
| String name = c.getName(); |
| if (name.equals("java.lang.Object") || |
| name.equals("java.lang.annotation.Annotation")) { |
| continue; |
| } |
| } |
| try { |
| setValue(methods[i].getName(), |
| methods[i].invoke(jsr175annotationObject,null), null); |
| } catch (IllegalAccessException e) { |
| //getLogger().warning(e); |
| } catch (InvocationTargetException e) { |
| //getLogger().warning(e); |
| } |
| } |
| } |
| */ |
| |
| } |