src/share/jaxws_classes/com/oracle/webservices/internal/api/message/BasePropertySet.java

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 374
72e03566f0a6
parent 0
373ffda63c9a
permissions
-rw-r--r--

merge

     1 /*
     2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Oracle in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    22  * or visit www.oracle.com if you need additional information or have any
    23  * questions.
    24  */
    26 package com.oracle.webservices.internal.api.message;
    28 import com.sun.istack.internal.NotNull;
    29 import com.sun.istack.internal.Nullable;
    31 import java.lang.reflect.Field;
    32 import java.lang.reflect.InvocationTargetException;
    33 import java.lang.reflect.Method;
    34 import java.security.AccessController;
    35 import java.security.PrivilegedAction;
    36 import java.util.AbstractMap;
    37 import java.util.HashMap;
    38 import java.util.HashSet;
    39 import java.util.Map;
    40 import java.util.Map.Entry;
    41 import java.util.Set;
    44 /**
    45  * A set of "properties" that can be accessed via strongly-typed fields
    46  * as well as reflexibly through the property name.
    47  *
    48  * @author Kohsuke Kawaguchi
    49  */
    50 @SuppressWarnings("SuspiciousMethodCalls")
    51 public abstract class BasePropertySet implements PropertySet {
    53     /**
    54      * Creates a new instance of TypedMap.
    55      */
    56     protected BasePropertySet() {
    57     }
    59     private Map<String,Object> mapView;
    61     /**
    62      * Represents the list of strongly-typed known properties
    63      * (keyed by property names.)
    64      *
    65      * <p>
    66      * Just giving it an alias to make the use of this class more fool-proof.
    67      */
    68     protected static class PropertyMap extends HashMap<String,Accessor> {
    70         // the entries are often being iterated through so performance can be improved
    71         // by their caching instead of iterating through the original (immutable) map each time
    72         transient PropertyMapEntry[] cachedEntries = null;
    74         PropertyMapEntry[] getPropertyMapEntries() {
    75             if (cachedEntries == null) {
    76                 cachedEntries = createPropertyMapEntries();
    77             }
    78             return cachedEntries;
    79         }
    81         private PropertyMapEntry[] createPropertyMapEntries() {
    82             final PropertyMapEntry[] modelEntries = new PropertyMapEntry[size()];
    83             int i = 0;
    84             for (final Entry<String, Accessor> e : entrySet()) {
    85                 modelEntries[i++] = new PropertyMapEntry(e.getKey(), e.getValue());
    86             }
    87             return modelEntries;
    88         }
    90     }
    92     /**
    93      * PropertyMapEntry represents a Map.Entry in the PropertyMap with more efficient access.
    94      */
    95     static public class PropertyMapEntry {
    96         public PropertyMapEntry(String k, Accessor v) {
    97             key = k; value = v;
    98         }
    99         String key;
   100         Accessor value;
   101     }
   103     /**
   104      * Map representing the Fields and Methods annotated with {@link PropertySet.Property}.
   105      * Model of {@link PropertySet} class.
   106      *
   107      * <p>
   108      * At the end of the derivation chain this method just needs to be implemented
   109      * as:
   110      *
   111      * <pre>
   112      * private static final PropertyMap model;
   113      * static {
   114      *   model = parse(MyDerivedClass.class);
   115      * }
   116      * protected PropertyMap getPropertyMap() {
   117      *   return model;
   118      * }
   119      * </pre>
   120      */
   121     protected abstract PropertyMap getPropertyMap();
   123     /**
   124      * This method parses a class for fields and methods with {@link PropertySet.Property}.
   125      */
   126     protected static PropertyMap parse(final Class clazz) {
   127         // make all relevant fields and methods accessible.
   128         // this allows runtime to skip the security check, so they runs faster.
   129         return AccessController.doPrivileged(new PrivilegedAction<PropertyMap>() {
   130             @Override
   131             public PropertyMap run() {
   132                 PropertyMap props = new PropertyMap();
   133                 for (Class c=clazz; c!=null; c=c.getSuperclass()) {
   134                     for (Field f : c.getDeclaredFields()) {
   135                         Property cp = f.getAnnotation(Property.class);
   136                         if(cp!=null) {
   137                             for(String value : cp.value()) {
   138                                 props.put(value, new FieldAccessor(f, value));
   139                             }
   140                         }
   141                     }
   142                     for (Method m : c.getDeclaredMethods()) {
   143                         Property cp = m.getAnnotation(Property.class);
   144                         if(cp!=null) {
   145                             String name = m.getName();
   146                             assert name.startsWith("get") || name.startsWith("is");
   148                             String setName = name.startsWith("is") ? "set"+name.substring(2) : // isFoo -> setFoo
   149                                                                      's'  +name.substring(1);  // getFoo -> setFoo
   150                             Method setter;
   151                             try {
   152                                 setter = clazz.getMethod(setName,m.getReturnType());
   153                             } catch (NoSuchMethodException e) {
   154                                 setter = null; // no setter
   155                             }
   156                             for(String value : cp.value()) {
   157                                 props.put(value, new MethodAccessor(m, setter, value));
   158                             }
   159                         }
   160                     }
   161                 }
   163                 return props;
   164             }
   165         });
   166     }
   168     /**
   169      * Represents a typed property defined on a {@link PropertySet}.
   170      */
   171     protected interface Accessor {
   172         String getName();
   173         boolean hasValue(PropertySet props);
   174         Object get(PropertySet props);
   175         void set(PropertySet props, Object value);
   176     }
   178     static final class FieldAccessor implements Accessor {
   179         /**
   180          * Field with the annotation.
   181          */
   182         private final Field f;
   184         /**
   185          * One of the values in {@link Property} annotation on {@link #f}.
   186          */
   187         private final String name;
   189         protected FieldAccessor(Field f, String name) {
   190             this.f = f;
   191             f.setAccessible(true);
   192             this.name = name;
   193         }
   195         @Override
   196         public String getName() {
   197             return name;
   198         }
   200         @Override
   201         public boolean hasValue(PropertySet props) {
   202             return get(props)!=null;
   203         }
   205         @Override
   206         public Object get(PropertySet props) {
   207             try {
   208                 return f.get(props);
   209             } catch (IllegalAccessException e) {
   210                 throw new AssertionError();
   211             }
   212         }
   214         @Override
   215         public void set(PropertySet props, Object value) {
   216             try {
   217                 f.set(props,value);
   218             } catch (IllegalAccessException e) {
   219                 throw new AssertionError();
   220             }
   221         }
   222     }
   224     static final class MethodAccessor implements Accessor {
   225         /**
   226          * Getter method.
   227          */
   228         private final @NotNull Method getter;
   229         /**
   230          * Setter method.
   231          * Some property is read-only.
   232          */
   233         private final @Nullable Method setter;
   235         /**
   236          * One of the values in {@link Property} annotation on {@link #getter}.
   237          */
   238         private final String name;
   240         protected MethodAccessor(Method getter, Method setter, String value) {
   241             this.getter = getter;
   242             this.setter = setter;
   243             this.name = value;
   244             getter.setAccessible(true);
   245             if (setter!=null) {
   246                 setter.setAccessible(true);
   247             }
   248         }
   250         @Override
   251         public String getName() {
   252             return name;
   253         }
   255         @Override
   256         public boolean hasValue(PropertySet props) {
   257             return get(props)!=null;
   258         }
   260         @Override
   261         public Object get(PropertySet props) {
   262             try {
   263                 return getter.invoke(props);
   264             } catch (IllegalAccessException e) {
   265                 throw new AssertionError();
   266             } catch (InvocationTargetException e) {
   267                 handle(e);
   268                 return 0;   // never reach here
   269             }
   270         }
   272         @Override
   273         public void set(PropertySet props, Object value) {
   274             if(setter==null) {
   275                 throw new ReadOnlyPropertyException(getName());
   276             }
   277             try {
   278                 setter.invoke(props,value);
   279             } catch (IllegalAccessException e) {
   280                 throw new AssertionError();
   281             } catch (InvocationTargetException e) {
   282                 handle(e);
   283             }
   284         }
   286         /**
   287          * Since we don't expect the getter/setter to throw a checked exception,
   288          * it should be possible to make the exception propagation transparent.
   289          * That's what we are trying to do here.
   290          */
   291         private Exception handle(InvocationTargetException e) {
   292             Throwable t = e.getTargetException();
   293             if (t instanceof Error) {
   294                 throw (Error)t;
   295             }
   296             if (t instanceof RuntimeException) {
   297                 throw (RuntimeException)t;
   298             }
   299             throw new Error(e);
   300         }
   301     }
   304     /**
   305      * Class allowing to work with PropertySet object as with a Map; it doesn't only allow to read properties from
   306      * the map but also to modify the map in a way it is in sync with original strongly typed fields. It also allows
   307      * (if necessary) to store additional properties those can't be found in strongly typed fields.
   308      *
   309      * @see com.sun.xml.internal.ws.api.PropertySet#asMap() method
   310      */
   311     final class MapView extends HashMap<String, Object> {
   313         // flag if it should allow store also different properties
   314         // than the from strongly typed fields
   315         boolean extensible;
   317         MapView(boolean extensible) {
   318                 super(getPropertyMap().getPropertyMapEntries().length);
   319             this.extensible = extensible;
   320             initialize();
   321         }
   323         public void initialize() {
   324             // iterate (cached) array instead of map to speed things up ...
   325             PropertyMapEntry[] entries = getPropertyMap().getPropertyMapEntries();
   326             for (PropertyMapEntry entry : entries) {
   327                 super.put(entry.key, entry.value);
   328             }
   329         }
   331         @Override
   332         public Object get(Object key) {
   333             Object o = super.get(key);
   334             if (o instanceof Accessor) {
   335                 return ((Accessor) o).get(BasePropertySet.this);
   336             } else {
   337                 return o;
   338             }
   339         }
   341         @Override
   342         public Set<Entry<String, Object>> entrySet() {
   343             Set<Entry<String, Object>> entries = new HashSet<Entry<String, Object>>();
   344             for (String key : keySet()) {
   345                 entries.add(new SimpleImmutableEntry<String, Object>(key, get(key)));
   346             }
   347             return entries;
   348         }
   350         @Override
   351         public Object put(String key, Object value) {
   353             Object o = super.get(key);
   354             if (o != null && o instanceof Accessor) {
   356                 Object oldValue = ((Accessor) o).get(BasePropertySet.this);
   357                 ((Accessor) o).set(BasePropertySet.this, value);
   358                 return oldValue;
   360             } else {
   362                 if (extensible) {
   363                     return super.put(key, value);
   364                 } else {
   365                     throw new IllegalStateException("Unknown property [" + key + "] for PropertySet [" +
   366                             BasePropertySet.this.getClass().getName() + "]");
   367                 }
   368             }
   369         }
   371         @Override
   372         public void clear() {
   373             for (String key : keySet()) {
   374                 remove(key);
   375             }
   376         }
   378         @Override
   379         public Object remove(Object key) {
   380             Object o;
   381             o = super.get(key);
   382             if (o instanceof Accessor) {
   383                 ((Accessor)o).set(BasePropertySet.this, null);
   384             }
   385             return super.remove(key);
   386         }
   387     }
   389     @Override
   390     public boolean containsKey(Object key) {
   391         Accessor sp = getPropertyMap().get(key);
   392         if (sp != null) {
   393             return sp.get(this) != null;
   394         }
   395         return false;
   396     }
   398     /**
   399      * Gets the name of the property.
   400      *
   401      * @param key
   402      *      This field is typed as {@link Object} to follow the {@link Map#get(Object)}
   403      *      convention, but if anything but {@link String} is passed, this method
   404      *      just returns null.
   405      */
   406     @Override
   407     public Object get(Object key) {
   408         Accessor sp = getPropertyMap().get(key);
   409         if (sp != null) {
   410             return sp.get(this);
   411         }
   412         throw new IllegalArgumentException("Undefined property "+key);
   413     }
   415     /**
   416      * Sets a property.
   417      *
   418      * <h3>Implementation Note</h3>
   419      * This method is slow. Code inside JAX-WS should define strongly-typed
   420      * fields in this class and access them directly, instead of using this.
   421      *
   422      * @throws ReadOnlyPropertyException
   423      *      if the given key is an alias of a strongly-typed field,
   424      *      and if the name object given is not assignable to the field.
   425      *
   426      * @see Property
   427      */
   428     @Override
   429     public Object put(String key, Object value) {
   430         Accessor sp = getPropertyMap().get(key);
   431         if(sp!=null) {
   432             Object old = sp.get(this);
   433             sp.set(this,value);
   434             return old;
   435         } else {
   436             throw new IllegalArgumentException("Undefined property "+key);
   437         }
   438     }
   440     /**
   441      * Checks if this {@link PropertySet} supports a property of the given name.
   442      */
   443     @Override
   444     public boolean supports(Object key) {
   445         return getPropertyMap().containsKey(key);
   446     }
   448     @Override
   449     public Object remove(Object key) {
   450         Accessor sp = getPropertyMap().get(key);
   451         if(sp!=null) {
   452             Object old = sp.get(this);
   453             sp.set(this,null);
   454             return old;
   455         } else {
   456             throw new IllegalArgumentException("Undefined property "+key);
   457         }
   458     }
   460     /**
   461      * Creates a {@link Map} view of this {@link PropertySet}.
   462      *
   463      * <p>
   464      * This map is partially live, in the sense that values you set to it
   465      * will be reflected to {@link PropertySet}.
   466      *
   467      * <p>
   468      * However, this map may not pick up changes made
   469      * to {@link PropertySet} after the view is created.
   470      *
   471      * @deprecated use newer implementation {@link PropertySet#asMap()} which produces
   472      * readwrite {@link Map}
   473      *
   474      * @return
   475      *      always non-null valid instance.
   476      */
   477     @Deprecated
   478     @Override
   479     public final Map<String,Object> createMapView() {
   480         final Set<Entry<String,Object>> core = new HashSet<Entry<String,Object>>();
   481         createEntrySet(core);
   483         return new AbstractMap<String, Object>() {
   484             @Override
   485             public Set<Entry<String,Object>> entrySet() {
   486                 return core;
   487             }
   488         };
   489     }
   491     /**
   492      * Creates a modifiable {@link Map} view of this {@link PropertySet}.
   493      * <p/>
   494      * Changes done on this {@link Map} or on {@link PropertySet} object work in both directions - values made to
   495      * {@link Map} are reflected to {@link PropertySet} and changes done using getters/setters on {@link PropertySet}
   496      * object are automatically reflected in this {@link Map}.
   497      * <p/>
   498      * If necessary, it also can hold other values (not present on {@link PropertySet}) -
   499      * {@see PropertySet#mapAllowsAdditionalProperties}
   500      *
   501      * @return always non-null valid instance.
   502      */
   503     @Override
   504     public Map<String, Object> asMap() {
   505         if (mapView == null) {
   506             mapView = createView();
   507         }
   508         return mapView;
   509     }
   511     protected Map<String, Object> createView() {
   512         return new MapView(mapAllowsAdditionalProperties());
   513     }
   515     /**
   516      * Used when constructing the {@link MapView} for this object - it controls if the {@link MapView} servers only to
   517      * access strongly typed values or allows also different values
   518      *
   519      * @return true if {@link Map} should allow also properties not defined as strongly typed fields
   520      */
   521     protected boolean mapAllowsAdditionalProperties() {
   522         return false;
   523     }
   525     protected void createEntrySet(Set<Entry<String,Object>> core) {
   526         for (final Entry<String, Accessor> e : getPropertyMap().entrySet()) {
   527             core.add(new Entry<String, Object>() {
   528                 @Override
   529                 public String getKey() {
   530                     return e.getKey();
   531                 }
   533                 @Override
   534                 public Object getValue() {
   535                     return e.getValue().get(BasePropertySet.this);
   536                 }
   538                 @Override
   539                 public Object setValue(Object value) {
   540                     Accessor acc = e.getValue();
   541                     Object old = acc.get(BasePropertySet.this);
   542                     acc.set(BasePropertySet.this,value);
   543                     return old;
   544                 }
   545             });
   546         }
   547     }
   548 }

mercurial