View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.jsr356.metadata;
20  
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentHashMap;
26  
27  import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
28  
29  /**
30   * An durable collection of {@link CoderMetadata}.
31   * <p>
32   * This is a write-only collection, and cannot be modified once initialized.
33   * 
34   * @param <T>
35   *            The type of coder ({@link javax.websocket.Decoder} or {@link javax.websocket.Encoder}
36   * @param <M>
37   *            The metadata for the coder
38   */
39  public abstract class CoderMetadataSet<T, M extends CoderMetadata<T>> implements Iterable<M>
40  {
41      /**
42       * Collection of metadatas
43       */
44      private final List<M> metadatas;
45      /**
46       * Collection of declared Coder classes
47       */
48      private final List<Class<? extends T>> coders;
49      /**
50       * Mapping of supported Type to metadata list index
51       */
52      private final Map<Class<?>, Integer> typeMap;
53      /**
54       * Mapping of Coder class to list of supported metadata
55       */
56      private final Map<Class<? extends T>, List<Integer>> implMap;
57  
58      protected CoderMetadataSet()
59      {
60          metadatas = new ArrayList<>();
61          coders = new ArrayList<>();
62          typeMap = new ConcurrentHashMap<>();
63          implMap = new ConcurrentHashMap<>();
64      }
65  
66      public void add(Class<? extends T> coder)
67      {
68          List<M> metadatas = discover(coder);
69          trackMetadata(metadatas);
70      }
71  
72      public List<M> addAll(Class<? extends T>[] coders)
73      {
74          List<M> metadatas = new ArrayList<>();
75  
76          for (Class<? extends T> coder : coders)
77          {
78              metadatas.addAll(discover(coder));
79          }
80  
81          trackMetadata(metadatas);
82          return metadatas;
83      }
84  
85      public List<M> addAll(List<Class<? extends T>> coders)
86      {
87          List<M> metadatas = new ArrayList<>();
88  
89          for (Class<? extends T> coder : coders)
90          {
91              metadatas.addAll(discover(coder));
92          }
93  
94          trackMetadata(metadatas);
95          return metadatas;
96      }
97  
98      /**
99       * Coder Specific discovery of Metadata for a specific coder.
100      * 
101      * @param coder
102      *            the coder to discover metadata in.
103      * @return the list of metadata discovered
104      * @throws InvalidWebSocketException
105      *             if unable to discover some metadata. Sucha as: a duplicate {@link CoderMetadata#getObjectType()} encountered, , or if unable to find the
106      *             concrete generic class reference for the coder, or if the provided coder is not valid per spec.
107      */
108     protected abstract List<M> discover(Class<? extends T> coder);
109 
110     public Class<? extends T> getCoder(Class<?> type)
111     {
112         M metadata = getMetadataByType(type);
113         if (metadata == null)
114         {
115             return null;
116         }
117         return metadata.getCoderClass();
118     }
119 
120     public List<Class<? extends T>> getList()
121     {
122         return coders;
123     }
124 
125     public List<M> getMetadataByImplementation(Class<? extends T> clazz)
126     {
127         List<Integer> indexes = implMap.get(clazz);
128         if (indexes == null)
129         {
130             return null;
131         }
132         List<M> ret = new ArrayList<>();
133         for (Integer idx : indexes)
134         {
135             ret.add(metadatas.get(idx));
136         }
137         return ret;
138     }
139 
140     public M getMetadataByType(Class<?> type)
141     {
142         Integer idx = typeMap.get(type);
143         if (idx == null)
144         {
145             // Quick lookup failed, try slower lookup via isAssignable instead
146             idx = getMetadataByAssignableType(type);
147             if (idx != null)
148             {
149                 // add new entry map
150                 typeMap.put(type,idx);
151             }
152         }
153 
154         // If idx is STILL null, we've got no match
155         if (idx == null)
156         {
157             return null;
158         }
159         return metadatas.get(idx);
160     }
161 
162     private Integer getMetadataByAssignableType(Class<?> type)
163     {
164         for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet())
165         {
166             if (entry.getKey().isAssignableFrom(type))
167             {
168                 return entry.getValue();
169             }
170         }
171 
172         return null;
173     }
174 
175     @Override
176     public Iterator<M> iterator()
177     {
178         return metadatas.iterator();
179     }
180 
181     @Override
182     public String toString()
183     {
184         StringBuilder builder = new StringBuilder();
185         builder.append(this.getClass().getSimpleName());
186         builder.append("[metadatas=");
187         builder.append(metadatas.size());
188         builder.append(",coders=");
189         builder.append(coders.size());
190         builder.append("]");
191         return builder.toString();
192     }
193 
194     protected void trackMetadata(List<M> metadatas)
195     {
196         for (M metadata : metadatas)
197         {
198             trackMetadata(metadata);
199         }
200     }
201 
202     protected void trackMetadata(M metadata)
203     {
204         synchronized (metadatas)
205         {
206             // Validate
207             boolean duplicate = false;
208 
209             // Is this metadata already declared?
210             if (metadatas.contains(metadata))
211             {
212                 duplicate = true;
213             }
214 
215             // Is this type already declared?
216             Class<?> type = metadata.getObjectType();
217             if (typeMap.containsKey(type))
218             {
219                 duplicate = true;
220             }
221 
222             if (duplicate)
223             {
224                 StringBuilder err = new StringBuilder();
225                 err.append("Duplicate decoder for type: ");
226                 err.append(type);
227                 err.append(" (class ").append(metadata.getCoderClass().getName());
228 
229                 // Get prior one
230                 M dup = getMetadataByType(type);
231                 err.append(" duplicates ");
232                 err.append(dup.getCoderClass().getName());
233                 err.append(")");
234                 throw new IllegalStateException(err.toString());
235             }
236 
237             // Track
238             Class<? extends T> coderClass = metadata.getCoderClass();
239             int newidx = metadatas.size();
240             metadatas.add(metadata);
241             coders.add(coderClass);
242             typeMap.put(type,newidx);
243 
244             List<Integer> indexes = implMap.get(coderClass);
245             if (indexes == null)
246             {
247                 indexes = new ArrayList<>();
248             }
249             if (indexes.contains(newidx))
250             {
251                 // possible duplicate, TODO: how?
252             }
253             indexes.add(newidx);
254             implMap.put(coderClass,indexes);
255         }
256     }
257 }