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    
020    package javax.activation;
021    
022    import java.io.BufferedReader;
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.FileReader;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.InputStreamReader;
029    import java.io.Reader;
030    import java.net.URL;
031    import java.security.Security;
032    import java.util.ArrayList;
033    import java.util.Collections;
034    import java.util.Enumeration;
035    import java.util.HashMap;
036    import java.util.Iterator;
037    import java.util.List;
038    import java.util.Map;
039    
040    /**
041     * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $
042     */
043    public class MailcapCommandMap extends CommandMap {
044        private final Map preferredCommands = new HashMap();
045        private final Map allCommands = new HashMap();
046        // commands identified as fallbacks...these are used last, and also used as wildcards.
047        private final Map fallbackCommands = new HashMap();
048        private URL url;
049    
050        public MailcapCommandMap() {
051            ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
052            // process /META-INF/mailcap.default
053            try {
054                InputStream is = MailcapCommandMap.class.getResourceAsStream("/META-INF/mailcap.default");
055                if (is != null) {
056                    try {
057                        parseMailcap(is);
058                    } finally {
059                        is.close();
060                    }
061                }
062            } catch (IOException e) {
063                // ignore
064            }
065    
066            // process /META-INF/mailcap resources
067            try {
068                Enumeration e = contextLoader.getResources("META-INF/mailcap");
069                while (e.hasMoreElements()) {
070                    url = ((URL) e.nextElement());
071                    try {
072                        InputStream is = url.openStream();
073                        try {
074                            parseMailcap(is);
075                        } finally {
076                            is.close();
077                        }
078                    } catch (IOException e1) {
079                        continue;
080                    }
081                }
082            } catch (SecurityException e) {
083                // ignore
084            } catch (IOException e) {
085                // ignore
086            }
087    
088            // process ${java.home}/lib/mailcap
089            try {
090                File file = new File(System.getProperty("java.home"), "lib/mailcap");
091                InputStream is = new FileInputStream(file);
092                try {
093                    parseMailcap(is);
094                } finally {
095                    is.close();
096                }
097            } catch (SecurityException e) {
098                // ignore
099            } catch (IOException e) {
100                // ignore
101            }
102    
103            // process ${user.home}/lib/mailcap
104            try {
105                File file = new File(System.getProperty("user.home"), ".mailcap");
106                InputStream is = new FileInputStream(file);
107                try {
108                    parseMailcap(is);
109                } finally {
110                    is.close();
111                }
112            } catch (SecurityException e) {
113                // ignore
114            } catch (IOException e) {
115                // ignore
116            }
117        }
118    
119        public MailcapCommandMap(String fileName) throws IOException {
120            this();
121            FileReader reader = new FileReader(fileName);
122            try {
123                parseMailcap(reader);
124            } finally {
125                reader.close();
126            }
127        }
128    
129        public MailcapCommandMap(InputStream is) {
130            this();
131            parseMailcap(is);
132        }
133    
134        private void parseMailcap(InputStream is) {
135            try {
136                parseMailcap(new InputStreamReader(is));
137            } catch (IOException e) {
138                // spec API means all we can do is swallow this
139            }
140        }
141    
142        void parseMailcap(Reader reader) throws IOException {
143            BufferedReader br = new BufferedReader(reader);
144            String line;
145            while ((line = br.readLine()) != null) {
146                addMailcap(line);
147            }
148        }
149    
150        public synchronized void addMailcap(String mail_cap) {
151            int index = 0;
152            // skip leading whitespace
153            index = skipSpace(mail_cap, index);
154            if (index == mail_cap.length() || mail_cap.charAt(index) == '#') {
155                return;
156            }
157    
158            // get primary type
159            int start = index;
160            index = getToken(mail_cap, index);
161            if (start == index) {
162                return;
163            }
164            String mimeType = mail_cap.substring(start, index);
165    
166            // skip any spaces after the primary type
167            index = skipSpace(mail_cap, index);
168            if (index == mail_cap.length() || mail_cap.charAt(index) == '#') {
169                return;
170            }
171    
172            // get sub-type
173            if (mail_cap.charAt(index) == '/') {
174                index = skipSpace(mail_cap, ++index);
175                start = index;
176                index = getToken(mail_cap, index);
177                mimeType = mimeType + '/' + mail_cap.substring(start, index);
178            } else {
179    
180                mimeType = mimeType + "/*";
181            }
182    
183            // we record all mappings using the lowercase version.
184            mimeType = mimeType.toLowerCase();
185    
186            // skip spaces after mime type
187            index = skipSpace(mail_cap, index);
188    
189            // expect a ';' to terminate field 1
190            if (index == mail_cap.length() || mail_cap.charAt(index) != ';') {
191                return;
192            }
193            index = getMText(mail_cap, index);
194            // expect a ';' to terminate field 2
195            if (index == mail_cap.length() || mail_cap.charAt(index) != ';') {
196                return;
197            }
198    
199            // we don't know which list this will be added to until we finish parsing, as there
200            // can be an x-java-fallback-entry parameter that moves this to the fallback list.
201            List commandList = new ArrayList();
202            // but by default, this is not a fallback.
203            boolean fallback = false;
204    
205            // parse fields
206            while (index < mail_cap.length() && mail_cap.charAt(index) == ';') {
207                index = skipSpace(mail_cap, index + 1);
208                start = index;
209                index = getToken(mail_cap, index);
210                String fieldName = mail_cap.substring(start, index).toLowerCase();
211                index = skipSpace(mail_cap, index);
212                if (index < mail_cap.length() && mail_cap.charAt(index) == '=') {
213                    index = skipSpace(mail_cap, index + 1);
214                    start = index;
215                    index = getMText(mail_cap, index);
216                    String value = mail_cap.substring(start, index);
217                    index = skipSpace(mail_cap, index);
218                    if (fieldName.startsWith("x-java-") && fieldName.length() > 7) {
219                        String command = fieldName.substring(7);
220                        value = value.trim();
221                        if (command.equals("fallback-entry")) {
222                            if (value.equals("true")) {
223                                fallback = true;
224                            }
225                        }
226                        else {
227                            // create a CommandInfo item and add it the accumulator
228                            CommandInfo info = new CommandInfo(command, value);
229                            commandList.add(info);
230                        }
231                    }
232                }
233            }
234            addCommands(mimeType, commandList, fallback);
235        }
236    
237        /**
238         * Add a parsed list of commands to the appropriate command list.
239         *
240         * @param mimeType The mimeType name this is added under.
241         * @param commands A List containing the command information.
242         * @param fallback The target list identifier.
243         */
244        private void addCommands(String mimeType, List commands, boolean fallback) {
245            // the target list changes based on the type of entry.
246            Map target = fallback ? fallbackCommands : preferredCommands;
247    
248            // now process
249            for (Iterator i = commands.iterator(); i.hasNext();) {
250                CommandInfo info = (CommandInfo)i.next();
251                addCommand(target, mimeType, info);
252                // if this is not a fallback position, then this to the allcommands list.
253                if (!fallback) {
254                    List cmdList = (List) allCommands.get(mimeType);
255                    if (cmdList == null) {
256                        cmdList = new ArrayList();
257                        allCommands.put(mimeType, cmdList);
258                    }
259                    cmdList.add(info);
260                }
261            }
262        }
263    
264    
265        /**
266         * Add a command to a target command list (preferred or fallback).
267         *
268         * @param commandList
269         *                 The target command list.
270         * @param mimeType The MIME type the command is associated with.
271         * @param command  The command information.
272         */
273        private void addCommand(Map commandList, String mimeType, CommandInfo command) {
274    
275            Map commands = (Map) commandList.get(mimeType);
276            if (commands == null) {
277                commands = new HashMap();
278                commandList.put(mimeType, commands);
279            }
280            commands.put(command.getCommandName(), command);
281        }
282    
283    
284        private int skipSpace(String s, int index) {
285            while (index < s.length() && Character.isWhitespace(s.charAt(index))) {
286                index++;
287            }
288            return index;
289        }
290    
291        private int getToken(String s, int index) {
292            while (index < s.length() && s.charAt(index) != '#' && !MimeType.isSpecial(s.charAt(index))) {
293                index++;
294            }
295            return index;
296        }
297    
298        private int getMText(String s, int index) {
299            while (index < s.length()) {
300                char c = s.charAt(index);
301                if (c == '#' || c == ';' || Character.isISOControl(c)) {
302                    return index;
303                }
304                if (c == '\\') {
305                    index++;
306                    if (index == s.length()) {
307                        return index;
308                    }
309                }
310                index++;
311            }
312            return index;
313        }
314    
315        public synchronized CommandInfo[] getPreferredCommands(String mimeType) {
316            // get the mimetype as a lowercase version.
317            mimeType = mimeType.toLowerCase();
318    
319            Map commands = (Map) preferredCommands.get(mimeType);
320            if (commands == null) {
321                commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType));
322            }
323    
324            Map fallbackCommands = getFallbackCommands(mimeType);
325    
326            // if we have fall backs, then we need to merge this stuff.
327            if (fallbackCommands != null) {
328                // if there's no command list, we can just use this as the master list.
329                if (commands == null) {
330                    commands = fallbackCommands;
331                }
332                else {
333                    // merge the two lists.  The ones in the commands list will take precedence.
334                    commands = mergeCommandMaps(commands, fallbackCommands);
335                }
336            }
337    
338            // now convert this into an array result.
339            if (commands == null) {
340                return new CommandInfo[0];
341            }
342            return (CommandInfo[]) commands.values().toArray(new CommandInfo[commands.size()]);
343        }
344    
345        private Map getFallbackCommands(String mimeType) {
346            Map commands = (Map) fallbackCommands.get(mimeType);
347    
348            // now we also need to search this as if it was a wildcard.  If we get a wildcard hit,
349            // we have to merge the two lists.
350            Map wildcardCommands = (Map)fallbackCommands.get(getWildcardMimeType(mimeType));
351            // no wildcard version
352            if (wildcardCommands == null) {
353                return commands;
354            }
355            // we need to merge these.
356            return mergeCommandMaps(commands, wildcardCommands);
357        }
358    
359    
360        private Map mergeCommandMaps(Map main, Map fallback) {
361            // create a cloned copy of the second map.  We're going to use a PutAll operation to
362            // overwrite any duplicates.
363            Map result = new HashMap(fallback);
364            result.putAll(main);
365    
366            return result;
367        }
368    
369        public synchronized CommandInfo[] getAllCommands(String mimeType) {
370            mimeType = mimeType.toLowerCase();
371            List exactCommands = (List) allCommands.get(mimeType);
372            if (exactCommands == null) {
373                exactCommands = Collections.EMPTY_LIST;
374            }
375            List wildCommands = (List) allCommands.get(getWildcardMimeType(mimeType));
376            if (wildCommands == null) {
377                wildCommands = Collections.EMPTY_LIST;
378            }
379    
380            Map fallbackCommands = getFallbackCommands(mimeType);
381            if (fallbackCommands == null) {
382                fallbackCommands = Collections.EMPTY_MAP;
383            }
384    
385    
386            CommandInfo[] result = new CommandInfo[exactCommands.size() + wildCommands.size() + fallbackCommands.size()];
387            int j = 0;
388            for (int i = 0; i < exactCommands.size(); i++) {
389                result[j++] = (CommandInfo) exactCommands.get(i);
390            }
391            for (int i = 0; i < wildCommands.size(); i++) {
392                result[j++] = (CommandInfo) wildCommands.get(i);
393            }
394    
395            for (Iterator i = fallbackCommands.keySet().iterator(); i.hasNext();) {
396                result[j++] = (CommandInfo) fallbackCommands.get((String)i.next());
397            }
398            return result;
399        }
400    
401        public synchronized CommandInfo getCommand(String mimeType, String cmdName) {
402            mimeType = mimeType.toLowerCase();
403            // strip any parameters from the supplied mimeType
404            int i = mimeType.indexOf(';');
405            if (i != -1) {
406                mimeType = mimeType.substring(0, i).trim();
407            }
408    
409            // search for an exact match
410            Map commands = (Map) preferredCommands.get(mimeType);
411            if (commands == null) {
412                // then a wild card match
413                commands = (Map) preferredCommands.get(getWildcardMimeType(mimeType));
414                if (commands == null) {
415                    // then fallback searches, both standard and wild card.
416                    commands = (Map) fallbackCommands.get(mimeType);
417                    if (commands == null) {
418                        commands = (Map) fallbackCommands.get(getWildcardMimeType(mimeType));
419                    }
420                    if (commands == null) {
421                        return null;
422                    }
423                }
424            }
425            return (CommandInfo) commands.get(cmdName.toLowerCase());
426        }
427    
428        private String getWildcardMimeType(String mimeType) {
429            int i = mimeType.indexOf('/');
430            if (i == -1) {
431                return mimeType + "/*";
432            } else {
433                return mimeType.substring(0, i + 1) + "*";
434            }
435        }
436    
437        public synchronized DataContentHandler createDataContentHandler(String mimeType) {
438    
439            CommandInfo info = getCommand(mimeType, "content-handler");
440            if (info == null) {
441                return null;
442            }
443    
444            ClassLoader cl = Thread.currentThread().getContextClassLoader();
445            if (cl == null) {
446                cl = getClass().getClassLoader();
447            }
448            try {
449                return (DataContentHandler) cl.loadClass(info.getCommandClass()).newInstance();
450            } catch (ClassNotFoundException e) {
451                return null;
452            } catch (IllegalAccessException e) {
453                return null;
454            } catch (InstantiationException e) {
455                return null;
456            }
457        }
458    }