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.Logger;
024    import org.apache.felix.framework.util.FelixConstants;
025    import org.apache.felix.framework.util.VersionRange;
026    import org.osgi.framework.*;
027    
028    public class R4LibraryClause
029    {
030        private final String[] m_libraryEntries;
031        private final String[] m_osnames;
032        private final String[] m_processors;
033        private final String[] m_osversions;
034        private final String[] m_languages;
035        private final String m_selectionFilter;
036    
037        public R4LibraryClause(String[] libraryEntries, String[] osnames,
038            String[] processors, String[] osversions, String[] languages,
039            String selectionFilter)
040        {
041            m_libraryEntries = libraryEntries;
042            m_osnames = osnames;
043            m_processors = processors;
044            m_osversions = osversions;
045            m_languages = languages;
046            m_selectionFilter = selectionFilter;
047        }
048    
049        public R4LibraryClause(R4LibraryClause library)
050        {
051            m_libraryEntries = library.m_libraryEntries;
052            m_osnames = library.m_osnames;
053            m_osversions = library.m_osversions;
054            m_processors = library.m_processors;
055            m_languages = library.m_languages;
056            m_selectionFilter = library.m_selectionFilter;
057        }
058    
059        public String[] getLibraryEntries()
060        {
061            return m_libraryEntries;
062        }
063    
064        public String[] getOSNames()
065        {
066            return m_osnames;
067        }
068    
069        public String[] getProcessors()
070        {
071            return m_processors;
072        }
073    
074        public String[] getOSVersions()
075        {
076            return m_osversions;
077        }
078    
079        public String[] getLanguages()
080        {
081            return m_languages;
082        }
083    
084        public String getSelectionFilter()
085        {
086            return m_selectionFilter;
087        }
088    
089        public boolean match(Map configMap) throws BundleException
090        {
091            String normal_osname = normalizeOSName((String) configMap.get(Constants.FRAMEWORK_OS_NAME));
092            String normal_processor = normalizeProcessor((String) configMap.get(Constants.FRAMEWORK_PROCESSOR));
093            String normal_osversion = normalizeOSVersion((String) configMap.get(Constants.FRAMEWORK_OS_VERSION));
094            String normal_language = (String) configMap.get(Constants.FRAMEWORK_LANGUAGE);
095    
096            // Check library's osname.
097            if (!checkOSNames(normal_osname, getOSNames()))
098            {
099                return false;
100            }
101    
102            // Check library's processor.
103            if (!checkProcessors(normal_processor, getProcessors()))
104            {
105                return false;
106            }
107    
108            // Check library's osversion if specified.
109            if ((getOSVersions() != null) &&
110                (getOSVersions().length > 0) &&
111                !checkOSVersions(normal_osversion, getOSVersions()))
112            {
113                return false;
114            }
115    
116            // Check library's language if specified.
117            if ((getLanguages() != null) &&
118                (getLanguages().length > 0) &&
119                !checkLanguages(normal_language, getLanguages()))
120            {
121                return false;
122            }
123    
124            // Check library's selection-filter if specified.
125            if ((getSelectionFilter() != null) &&
126                (getSelectionFilter().length() >= 0) &&
127                !checkSelectionFilter(configMap, getSelectionFilter()))
128            {
129                return false;
130            }
131    
132            return true;
133        }
134    
135        private boolean checkOSNames(String currentOSName, String[] osnames)
136        {
137            boolean win32 = currentOSName.startsWith("win") &&
138                (currentOSName.equals("windows95")
139                || currentOSName.equals("windows98")
140                || currentOSName.equals("windowsnt")
141                || currentOSName.equals("windows2000")
142                || currentOSName.equals("windows2003")
143                || currentOSName.equals("windowsxp")
144                || currentOSName.equals("windowsce")
145                || currentOSName.equals("windowsvista")
146                || currentOSName.equals("windows7"));
147    
148            for (int i = 0; (osnames != null) && (i < osnames.length); i++)
149            {
150                if (osnames[i].equals(currentOSName) ||
151                    ("win32".equals(osnames[i]) && win32))
152                {
153                    return true;
154                }
155            }
156            return false;
157        }
158    
159        private boolean checkProcessors(String currentProcessor, String[] processors)
160        {
161            for (int i = 0; (processors != null) && (i < processors.length); i++)
162            {
163                if (processors[i].equals(currentProcessor))
164                {
165                    return true;
166                }
167            }
168            return false;
169        }
170    
171        private boolean checkOSVersions(String currentOSVersion, String[] osversions)
172            throws BundleException
173        {
174            for (int i = 0; (osversions != null) && (i < osversions.length); i++)
175            {
176                try
177                {
178                    VersionRange range = VersionRange.parse(osversions[i]);
179                    if (range.isInRange(new Version(currentOSVersion)))
180                    {
181                        return true;
182                    }
183                }
184                catch (Exception ex)
185                {
186                    throw new BundleException(
187                        "Error evaluating osversion: " + osversions[i], ex);
188                }
189            }
190            return false;
191        }
192    
193        private boolean checkLanguages(String currentLanguage, String[] languages)
194        {
195            for (int i = 0; (languages != null) && (i < languages.length); i++)
196            {
197                if (languages[i].equals(currentLanguage))
198                {
199                    return true;
200                }
201            }
202            return false;
203        }
204    
205        private boolean checkSelectionFilter(Map configMap, String expr)
206            throws BundleException
207        {
208            // Get all framework properties
209            Dictionary dict = new Hashtable();
210            for (Iterator i = configMap.keySet().iterator(); i.hasNext(); )
211            {
212                Object key = i.next();
213                dict.put(key, configMap.get(key));
214            }
215            // Compute expression
216            try
217            {
218                Filter filter = FrameworkUtil.createFilter(expr);
219                return filter.match(dict);
220            }
221            catch (Exception ex)
222            {
223                throw new BundleException(
224                    "Error evaluating filter expression: " + expr, ex);
225            }
226        }
227    
228        public static R4LibraryClause parse(Logger logger, String s)
229        {
230            try
231            {
232                if ((s == null) || (s.length() == 0))
233                {
234                    return null;
235                }
236    
237                if (s.equals(FelixConstants.BUNDLE_NATIVECODE_OPTIONAL))
238                {
239                    return new R4LibraryClause(null, null, null, null, null, null);
240                }
241    
242                // The tokens are separated by semicolons and may include
243                // any number of libraries along with one set of associated
244                // properties.
245                StringTokenizer st = new StringTokenizer(s, ";");
246                String[] libEntries = new String[st.countTokens()];
247                List osNameList = new ArrayList();
248                List osVersionList = new ArrayList();
249                List processorList = new ArrayList();
250                List languageList = new ArrayList();
251                String selectionFilter = null;
252                int libCount = 0;
253                while (st.hasMoreTokens())
254                {
255                    String token = st.nextToken().trim();
256                    if (token.indexOf('=') < 0)
257                    {
258                        // Remove the slash, if necessary.
259                        libEntries[libCount] = (token.charAt(0) == '/')
260                            ? token.substring(1)
261                            : token;
262                        libCount++;
263                    }
264                    else
265                    {
266                        // Check for valid native library properties; defined as
267                        // a property name, an equal sign, and a value.
268                        // NOTE: StringTokenizer can not be used here because
269                        // a value can contain one or more "=" too, e.g.,
270                        // selection-filter="(org.osgi.framework.windowing.system=gtk)"
271                        String property = null;
272                        String value = null;
273                        if (!(token.indexOf("=") > 1))
274                        {
275                            throw new IllegalArgumentException(
276                                "Bundle manifest native library entry malformed: " + token);
277                        }
278                        else
279                        {
280                            property = (token.substring(0, token.indexOf("=")))
281                                .trim().toLowerCase();
282                            value = (token.substring(token.indexOf("=") + 1, token
283                                .length())).trim();
284                        }
285    
286                        // Values may be quoted, so remove quotes if present.
287                        if (value.charAt(0) == '"')
288                        {
289                            // This should always be true, otherwise the
290                            // value wouldn't be properly quoted, but we
291                            // will check for safety.
292                            if (value.charAt(value.length() - 1) == '"')
293                            {
294                                value = value.substring(1, value.length() - 1);
295                            }
296                            else
297                            {
298                                value = value.substring(1);
299                            }
300                        }
301                        // Add the value to its corresponding property list.
302                        if (property.equals(Constants.BUNDLE_NATIVECODE_OSNAME))
303                        {
304                            osNameList.add(normalizeOSName(value));
305                        }
306                        else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
307                        {
308                            osVersionList.add(normalizeOSVersion(value));
309                        }
310                        else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
311                        {
312                            processorList.add(normalizeProcessor(value));
313                        }
314                        else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE))
315                        {
316                            languageList.add(value);
317                        }
318                        else if (property.equals(Constants.SELECTION_FILTER_ATTRIBUTE))
319                        {
320    // TODO: NATIVE - I believe we can have multiple selection filters too.
321                            selectionFilter = value;
322                        }
323                    }
324                }
325    
326                if (libCount == 0)
327                {
328                    return null;
329                }
330    
331                // Shrink lib file array.
332                String[] actualLibEntries = new String[libCount];
333                System.arraycopy(libEntries, 0, actualLibEntries, 0, libCount);
334                return new R4LibraryClause(
335                    actualLibEntries,
336                    (String[]) osNameList.toArray(new String[osNameList.size()]),
337                    (String[]) processorList.toArray(new String[processorList.size()]),
338                    (String[]) osVersionList.toArray(new String[osVersionList.size()]),
339                    (String[]) languageList.toArray(new String[languageList.size()]),
340                    selectionFilter);
341            }
342            catch (RuntimeException ex)
343            {
344                logger.log(Logger.LOG_ERROR,
345                    "Error parsing native library header.", ex);
346                throw ex;
347            }
348        }
349    
350        public static String normalizeOSName(String value)
351        {
352            value = value.toLowerCase();
353    
354            if (value.startsWith("win"))
355            {
356                String os = "win";
357                if (value.indexOf("32") >= 0 || value.indexOf("*") >= 0)
358                {
359                    os = "win32";
360                }
361                else if (value.indexOf("95") >= 0)
362                {
363                    os = "windows95";
364                }
365                else if (value.indexOf("98") >= 0)
366                {
367                    os = "windows98";
368                }
369                else if (value.indexOf("nt") >= 0)
370                {
371                    os = "windowsnt";
372                }
373                else if (value.indexOf("2000") >= 0)
374                {
375                    os = "windows2000";
376                }
377                else if (value.indexOf("2003") >= 0)
378                {
379                    os = "windows2003";
380                }
381                else if (value.indexOf("xp") >= 0)
382                {
383                    os = "windowsxp";
384                }
385                else if (value.indexOf("ce") >= 0)
386                {
387                    os = "windowsce";
388                }
389                else if (value.indexOf("vista") >= 0)
390                {
391                    os = "windowsvista";
392                }
393                // will need better test here if any future Windows version has a 7 in it!
394                else if (value.indexOf("7") >= 0)
395                {
396                    os = "windows7";
397                }
398                return os;
399            }
400            else if (value.startsWith("linux"))
401            {
402                return "linux";
403            }
404            else if (value.startsWith("aix"))
405            {
406                return "aix";
407            }
408            else if (value.startsWith("digitalunix"))
409            {
410                return "digitalunix";
411            }
412            else if (value.startsWith("hpux"))
413            {
414                return "hpux";
415            }
416            else if (value.startsWith("irix"))
417            {
418                return "irix";
419            }
420            else if (value.startsWith("macos") || value.startsWith("mac os"))
421            {
422                return "macos";
423            }
424            else if (value.startsWith("netware"))
425            {
426                return "netware";
427            }
428            else if (value.startsWith("openbsd"))
429            {
430                return "openbsd";
431            }
432            else if (value.startsWith("netbsd"))
433            {
434                return "netbsd";
435            }
436            else if (value.startsWith("os2") || value.startsWith("os/2"))
437            {
438                return "os2";
439            }
440            else if (value.startsWith("qnx") || value.startsWith("procnto"))
441            {
442                return "qnx";
443            }
444            else if (value.startsWith("solaris"))
445            {
446                return "solaris";
447            }
448            else if (value.startsWith("sunos"))
449            {
450                return "sunos";
451            }
452            else if (value.startsWith("vxworks"))
453            {
454                return "vxworks";
455            }
456            return value;
457        }
458    
459        public static String normalizeProcessor(String value)
460        {
461            value = value.toLowerCase();
462    
463            if (value.startsWith("x86-64") || value.startsWith("amd64") || 
464                value.startsWith("em64") || value.startsWith("x86_64"))
465            {
466                return "x86-64";
467            }
468            else if (value.startsWith("x86") || value.startsWith("pentium")
469                || value.startsWith("i386") || value.startsWith("i486")
470                || value.startsWith("i586") || value.startsWith("i686"))
471            {
472                return "x86";
473            }
474            else if (value.startsWith("68k"))
475            {
476                return "68k";
477            }
478            else if (value.startsWith("arm"))
479            {
480                return "arm";
481            }
482            else if (value.startsWith("alpha"))
483            {
484                return "alpha";
485            }
486            else if (value.startsWith("ignite") || value.startsWith("psc1k"))
487            {
488                return "ignite";
489            }
490            else if (value.startsWith("mips"))
491            {
492                return "mips";
493            }
494            else if (value.startsWith("parisc"))
495            {
496                return "parisc";
497            }
498            else if (value.startsWith("powerpc") || value.startsWith("power")
499                || value.startsWith("ppc"))
500            {
501                return "powerpc";
502            }
503            else if (value.startsWith("sparc"))
504            {
505                return "sparc";
506            }
507            return value;
508        }
509    
510        public static String normalizeOSVersion(String value)
511        {
512            // Header: 'Bundle-NativeCode', Parameter: 'osversion'
513            // Standardized 'osversion': major.minor.micro, only digits
514            try
515            {
516                return VersionRange.parse(value).toString();
517            }
518            catch (Exception ex)
519            {
520                return Version.emptyVersion.toString();
521            }
522        }
523    }