001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.felix.framework.util.manifestparser;
020    
021    import java.util.*;
022    
023    import org.apache.felix.framework.util.Util;
024    import org.apache.felix.moduleloader.ICapability;
025    import org.apache.felix.moduleloader.IModule;
026    import org.osgi.framework.Constants;
027    import org.osgi.framework.Version;
028    
029    public class Capability implements ICapability, Comparable
030    {
031        private final IModule m_module;
032        private final String m_namespace;
033        private final R4Directive[] m_directives;
034        private final R4Attribute[] m_attributes;
035        private final String[] m_uses;
036        private final String[][] m_includeFilter;
037        private final String[][] m_excludeFilter;
038        private volatile Map m_attrMap;
039    
040        // Cached properties for performance reasons.
041        private final String m_pkgName;
042        private final Version m_pkgVersion;
043    
044        public Capability(IModule module, String namespace, R4Directive[] dirs, R4Attribute[] attrs)
045        {
046            m_module = module;
047            m_namespace = namespace;
048            m_directives = dirs;
049            m_attributes = attrs;
050    
051            // Find all export directives: uses, mandatory, include, and exclude.
052            String mandatory = "";
053            String[] uses = new String[0];
054            String[][] includeFilter = null, excludeFilter = null;
055            for (int dirIdx = 0; (m_directives != null) && (dirIdx < m_directives.length); dirIdx++)
056            {
057                if (m_directives[dirIdx].getName().equals(Constants.USES_DIRECTIVE))
058                {
059                    // Parse these uses directive.
060                    StringTokenizer tok = new StringTokenizer(m_directives[dirIdx].getValue(), ",");
061                    uses = new String[tok.countTokens()];
062                    for (int i = 0; i < uses.length; i++)
063                    {
064                        uses[i] = tok.nextToken().trim();
065                    }
066                }
067                else if (m_directives[dirIdx].getName().equals(Constants.MANDATORY_DIRECTIVE))
068                {
069                    mandatory = m_directives[dirIdx].getValue();
070                }
071                else if (m_directives[dirIdx].getName().equals(Constants.INCLUDE_DIRECTIVE))
072                {
073                    String[] ss = ManifestParser.parseDelimitedString(m_directives[dirIdx].getValue(), ",");
074                    includeFilter = new String[ss.length][];
075                    for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
076                    {
077                        includeFilter[filterIdx] = Util.parseSubstring(ss[filterIdx]);
078                    }
079                }
080                else if (m_directives[dirIdx].getName().equals(Constants.EXCLUDE_DIRECTIVE))
081                {
082                    String[] ss = ManifestParser.parseDelimitedString(m_directives[dirIdx].getValue(), ",");
083                    excludeFilter = new String[ss.length][];
084                    for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
085                    {
086                        excludeFilter[filterIdx] = Util.parseSubstring(ss[filterIdx]);
087                    }
088                }
089            }
090    
091            // Set final values.
092            m_uses = uses;
093            m_includeFilter = includeFilter;
094            m_excludeFilter = excludeFilter;
095    
096            // Parse mandatory directive and mark specified
097            // attributes as mandatory.
098            StringTokenizer tok = new StringTokenizer(mandatory, ", ");
099            while (tok.hasMoreTokens())
100            {
101                // Get attribute name.
102                String attrName = tok.nextToken().trim();
103                // Find attribute and mark it as mandatory.
104                boolean found = false;
105                for (int i = 0; (!found) && (i < m_attributes.length); i++)
106                {
107                    if (m_attributes[i].getName().equals(attrName))
108                    {
109                        m_attributes[i] = new R4Attribute(
110                            m_attributes[i].getName(),
111                            m_attributes[i].getValue(), true);
112                        found = true;
113                    }
114                }
115                // If a specified mandatory attribute was not found,
116                // then error.
117                if (!found)
118                {
119                    throw new IllegalArgumentException(
120                        "Mandatory attribute '" + attrName + "' does not exist.");
121                }
122            }
123    
124            // For performance reasons, find the package name and version properties.
125            String pkgName = null;
126            Version pkgVersion = Version.emptyVersion;
127            for (int i = 0; i < m_attributes.length; i++)
128            {
129                if (m_attributes[i].getName().equals(ICapability.PACKAGE_PROPERTY))
130                {
131                    pkgName = (String) m_attributes[i].getValue();
132                }
133                else if (m_attributes[i].getName().equals(ICapability.VERSION_PROPERTY))
134                {
135                    pkgVersion = (Version) m_attributes[i].getValue();
136                }
137            }
138    
139            // Set final values.
140            m_pkgName = pkgName;
141            m_pkgVersion = pkgVersion;
142        }
143    
144        public IModule getModule()
145        {
146            return m_module;
147        }
148    
149        public String getNamespace()
150        {
151            return m_namespace;
152        }
153    
154    // TODO: RB - Determine how to eliminate these non-generic methods;
155    //            at least make sure they are not used in the generic resolver.
156        public String getPackageName()
157        {
158            return m_pkgName;
159        }
160    
161        public Version getPackageVersion()
162        {
163            return m_pkgVersion;
164        }
165    
166        public R4Directive[] getDirectives()
167        {
168            // TODO: RB - We should return copies of the arrays probably.
169            return m_directives;
170        }
171    
172        public R4Attribute[] getAttributes()
173        {
174            // TODO: RB - We should return copies of the arrays probably.
175            return m_attributes;
176        }
177    
178        public String[] getUses()
179        {
180            // TODO: RB - We should return copies of the arrays probably.
181            return m_uses;
182        }
183    
184        public boolean isIncluded(String name)
185        {
186            if ((m_includeFilter == null) && (m_excludeFilter == null))
187            {
188                return true;
189            }
190    
191            // Get the class name portion of the target class.
192            String className = Util.getClassName(name);
193    
194            // If there are no include filters then all classes are included
195            // by default, otherwise try to find one match.
196            boolean included = (m_includeFilter == null);
197            for (int i = 0;
198                (!included) && (m_includeFilter != null) && (i < m_includeFilter.length);
199                i++)
200            {
201                included = Util.checkSubstring(m_includeFilter[i], className);
202            }
203    
204            // If there are no exclude filters then no classes are excluded
205            // by default, otherwise try to find one match.
206            boolean excluded = false;
207            for (int i = 0;
208                (!excluded) && (m_excludeFilter != null) && (i < m_excludeFilter.length);
209                i++)
210            {
211                excluded = Util.checkSubstring(m_excludeFilter[i], className);
212            }
213            return included && !excluded;
214        }
215    
216    // TODO: RB - Terminology mismatch property vs. attribute.
217        public Map getProperties()
218        {
219            if (m_attrMap == null)
220            {
221                m_attrMap = new Map() {
222    
223                    public int size()
224                    {
225                        // A name and version attribute is always present, since it has a
226                        // default value.
227                        return m_attributes.length + 2;
228                    }
229    
230                    public boolean isEmpty()
231                    {
232                        // A version attribute is always present, since it has a
233                        // default value.
234                        return false;
235                    }
236    
237                    public boolean containsKey(Object key)
238                    {
239                        return (get(key) != null);
240                    }
241    
242                    public boolean containsValue(Object value)
243                    {
244                        // Check the package name.
245                        if (m_pkgName.equals(value))
246                        {
247                            return true;
248                        }
249    
250                        // Check the package version.
251                        if (m_pkgVersion.equals(value))
252                        {
253                            return true;
254                        }
255    
256                        // Check all attributes.
257                        for (int i = 0; i < m_attributes.length; i++)
258                        {
259                            if (m_attributes[i].getValue().equals(value))
260                            {
261                                return true;
262                            }
263                        }
264    
265                        return false;
266                    }
267    
268                    public Object get(Object key)
269                    {
270                        if (ICapability.PACKAGE_PROPERTY.equals(key))
271                        {
272                            return m_pkgName;
273                        }
274                        else if (ICapability.VERSION_PROPERTY.equals(key))
275                        {
276                            return m_pkgVersion;
277                        }
278    
279                        for (int i = 0; i < m_attributes.length; i++)
280                        {
281                            if (m_attributes[i].getName().equals(key))
282                            {
283                                return m_attributes[i].getValue();
284                            }
285                        }
286    
287                        return null;
288                    }
289    
290                    public Object put(Object key, Object value)
291                    {
292                        throw new UnsupportedOperationException("Map.put() not implemented.");
293                    }
294    
295                    public Object remove(Object key)
296                    {
297                        throw new UnsupportedOperationException("Map.remove() not implemented.");
298                    }
299    
300                    public void putAll(Map t)
301                    {
302                        throw new UnsupportedOperationException("Map.putAll() not implemented.");
303                    }
304    
305                    public void clear()
306                    {
307                        throw new UnsupportedOperationException("Map.clear() not implemented.");
308                    }
309    
310                    public Set keySet()
311                    {
312                        Set set = new HashSet();
313                        set.add(ICapability.PACKAGE_PROPERTY);
314                        set.add(ICapability.VERSION_PROPERTY);
315                        for (int i = 0; i < m_attributes.length; i++)
316                        {
317                            set.add(m_attributes[i].getName());
318                        }
319                        return set;
320                    }
321    
322                    public Collection values()
323                    {
324                        throw new UnsupportedOperationException("Map.values() not implemented.");
325                    }
326    
327                    public Set entrySet()
328                    {
329                        throw new UnsupportedOperationException("Map.entrySet() not implemented.");
330                    }
331                };
332            }
333            return m_attrMap;
334        }
335    
336        public int compareTo(Object o)
337        {
338            Capability cap = (Capability) o;
339            Version thisVersion = null;
340            Version version = null;
341            if (getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
342            {
343                thisVersion = getPackageVersion();
344                version = cap.getPackageVersion();
345            }
346            else if (getNamespace().equals(ICapability.MODULE_NAMESPACE))
347            {
348                thisVersion = (Version) getProperties().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
349                version = (Version) cap.getProperties().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
350            }
351            if ((thisVersion != null) && (version != null))
352            {
353                int cmp = thisVersion.compareTo(version);
354                if (cmp < 0)
355                {
356                    return 1;
357                }
358                else if (cmp > 0)
359                {
360                    return -1;
361                }
362                else
363                {
364                    long thisId = m_module.getBundle().getBundleId();
365                    long id = cap.getModule().getBundle().getBundleId();
366                    if (thisId < id)
367                    {
368                        return -1;
369                    }
370                    else if (thisId > id)
371                    {
372                        return 1;
373                    }
374                    return 0;
375                }
376            }
377            else
378            {
379                return -1;
380            }
381        }
382    
383        public String toString()
384        {
385            StringBuffer sb = new StringBuffer();
386            sb.append(getNamespace());
387            for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
388            {
389                sb.append(";");
390                sb.append(m_directives[i].getName());
391                sb.append(":=\"");
392                sb.append(m_directives[i].getValue());
393                sb.append("\"");
394            }
395            for (int i = 0; (m_attributes != null) && (i < m_attributes.length); i++)
396            {
397                sb.append(";");
398                sb.append(m_attributes[i].getName());
399                sb.append("=\"");
400                sb.append(m_attributes[i].getValue());
401                sb.append("\"");
402            }
403            return sb.toString();
404        }
405    }