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