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 
17 package com.android.layoutlib.bridge.impl;
18 
19 
20 import org.xmlpull.v1.XmlPullParser;
21 import org.xmlpull.v1.XmlPullParserException;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 
26 import java.io.BufferedInputStream;
27 import java.io.ByteArrayInputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStream;
33 
34 /**
35  * A factory for {@link XmlPullParser}.
36  *
37  */
38 public 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 
setParserFactory( @ullable com.android.ide.common.rendering.api.ParserFactory parserFactory)48     public static void setParserFactory(
49             @Nullable com.android.ide.common.rendering.api.ParserFactory parserFactory) {
50         sParserFactory = parserFactory;
51     }
52 
53     @NonNull
create(@onNull File f)54     public static XmlPullParser create(@NonNull File f)
55             throws XmlPullParserException, FileNotFoundException {
56         InputStream stream = new FileInputStream(f);
57         return create(stream, f.getName(), f.length());
58     }
59 
60     @NonNull
create(@onNull InputStream stream, @Nullable String name)61     public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
62         throws XmlPullParserException {
63         return create(stream, name, -1);
64     }
65 
66     @NonNull
create(@onNull InputStream stream, @Nullable String name, long size)67     private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
68             long size) throws XmlPullParserException {
69         XmlPullParser parser = instantiateParser(name);
70 
71         stream = readAndClose(stream, name, size);
72 
73         parser.setInput(stream, ENCODING);
74         return parser;
75     }
76 
77     @NonNull
instantiateParser(@ullable String name)78     public static XmlPullParser instantiateParser(@Nullable String name)
79             throws XmlPullParserException {
80         if (sParserFactory == null) {
81             throw new XmlPullParserException("ParserFactory not initialized.");
82         }
83         XmlPullParser parser = sParserFactory.createParser(name);
84         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
85         return parser;
86     }
87 
88     @NonNull
readAndClose(@onNull InputStream stream, @Nullable String name, long size)89     private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name,
90             long size) throws XmlPullParserException {
91         // just a sanity check. It's doubtful we'll have such big files!
92         if (size > Integer.MAX_VALUE) {
93             throw new XmlPullParserException("File " + name + " is too big to be parsed");
94         }
95         int intSize = (int) size;
96 
97         // create a buffered reader to facilitate reading.
98         BufferedInputStream bufferedStream = new BufferedInputStream(stream);
99         try {
100             int avail;
101             if (intSize != -1) {
102                 avail = intSize;
103             } else {
104                 // get the size to read.
105                 avail = bufferedStream.available();
106             }
107 
108             // create the initial buffer and read it.
109             byte[] buffer = new byte[avail];
110             int read = stream.read(buffer);
111 
112             // this is the easy case.
113             if (read == intSize) {
114                 return new ByteArrayInputStream(buffer);
115             }
116 
117             // check if there is more to read (read() does not necessarily read all that
118             // available() returned!)
119             while ((avail = bufferedStream.available()) > 0) {
120                 if (read + avail > buffer.length) {
121                     // just allocate what is needed. We're mostly reading small files
122                     // so it shouldn't be too problematic.
123                     byte[] moreBuffer = new byte[read + avail];
124                     System.arraycopy(buffer, 0, moreBuffer, 0, read);
125                     buffer = moreBuffer;
126                 }
127 
128                 read += stream.read(buffer, read, avail);
129             }
130 
131             // return a new stream encapsulating this buffer.
132             return new ByteArrayInputStream(buffer);
133 
134         } catch (IOException e) {
135             throw new XmlPullParserException("Failed to read " + name, null, e);
136         } finally {
137             try {
138                 bufferedStream.close();
139             } catch (IOException ignored) {
140             }
141         }
142     }
143 }
144