1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.layoutlib.bridge.impl;
18
19
20import org.xmlpull.v1.XmlPullParser;
21import org.xmlpull.v1.XmlPullParserException;
22
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25
26import java.io.BufferedInputStream;
27import java.io.ByteArrayInputStream;
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.FileNotFoundException;
31import java.io.IOException;
32import java.io.InputStream;
33
34/**
35 * A factory for {@link XmlPullParser}.
36 *
37 */
38public class ParserFactory {
39
40    public final static boolean LOG_PARSER = false;
41
42    private final static String ENCODING = "UTF-8"; //$NON-NLS-1$
43
44    // Used to get a new XmlPullParser from the client.
45    @Nullable
46    private static com.android.ide.common.rendering.api.ParserFactory sParserFactory;
47
48    public static void setParserFactory(
49            @Nullable com.android.ide.common.rendering.api.ParserFactory parserFactory) {
50        sParserFactory = parserFactory;
51    }
52
53    @NonNull
54    public static XmlPullParser create(@NonNull File f)
55            throws XmlPullParserException, FileNotFoundException {
56        return create(f, false);
57    }
58
59    public static XmlPullParser create(@NonNull File f, boolean isLayout)
60      throws XmlPullParserException, FileNotFoundException {
61        InputStream stream = new FileInputStream(f);
62        return create(stream, f.getName(), f.length(), isLayout);
63    }
64    @NonNull
65    public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
66        throws XmlPullParserException {
67        return create(stream, name, -1, false);
68    }
69
70    @NonNull
71    private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
72            long size, boolean isLayout) throws XmlPullParserException {
73        XmlPullParser parser = instantiateParser(name);
74
75        stream = readAndClose(stream, name, size);
76
77        parser.setInput(stream, ENCODING);
78        if (isLayout) {
79            try {
80                return new LayoutParserWrapper(parser).peekTillLayoutStart();
81            } catch (IOException e) {
82                throw new XmlPullParserException(null, parser, e);
83            }
84        }
85        return parser;
86    }
87
88    @NonNull
89    public static XmlPullParser instantiateParser(@Nullable String name)
90            throws XmlPullParserException {
91        if (sParserFactory == null) {
92            throw new XmlPullParserException("ParserFactory not initialized.");
93        }
94        XmlPullParser parser = sParserFactory.createParser(name);
95        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
96        return parser;
97    }
98
99    @NonNull
100    private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name,
101            long size) throws XmlPullParserException {
102        // just a sanity check. It's doubtful we'll have such big files!
103        if (size > Integer.MAX_VALUE) {
104            throw new XmlPullParserException("File " + name + " is too big to be parsed");
105        }
106        int intSize = (int) size;
107
108        // create a buffered reader to facilitate reading.
109        BufferedInputStream bufferedStream = new BufferedInputStream(stream);
110        try {
111            int avail;
112            if (intSize != -1) {
113                avail = intSize;
114            } else {
115                // get the size to read.
116                avail = bufferedStream.available();
117            }
118
119            // create the initial buffer and read it.
120            byte[] buffer = new byte[avail];
121            int read = stream.read(buffer);
122
123            // this is the easy case.
124            if (read == intSize) {
125                return new ByteArrayInputStream(buffer);
126            }
127
128            // check if there is more to read (read() does not necessarily read all that
129            // available() returned!)
130            while ((avail = bufferedStream.available()) > 0) {
131                if (read + avail > buffer.length) {
132                    // just allocate what is needed. We're mostly reading small files
133                    // so it shouldn't be too problematic.
134                    byte[] moreBuffer = new byte[read + avail];
135                    System.arraycopy(buffer, 0, moreBuffer, 0, read);
136                    buffer = moreBuffer;
137                }
138
139                read += stream.read(buffer, read, avail);
140            }
141
142            // return a new stream encapsulating this buffer.
143            return new ByteArrayInputStream(buffer);
144
145        } catch (IOException e) {
146            throw new XmlPullParserException("Failed to read " + name, null, e);
147        } finally {
148            try {
149                bufferedStream.close();
150            } catch (IOException ignored) {
151            }
152        }
153    }
154}
155