1/*
2 * Copyright (C) 2007-2008 Esmertec AG.
3 * Copyright (C) 2007-2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.im.imps;
19
20import android.os.Parcel;
21
22import com.android.im.engine.Address;
23import com.android.im.engine.ImEntity;
24
25/**
26 * The abstract representation of an IMPS address.
27 */
28public abstract class ImpsAddress extends Address {
29    private static final char[] SPECIALS = {'/', '@', '+'};
30
31    protected String mUser;
32    protected String mResource;
33    protected String mDomain;
34
35    private String mFullAddress;
36
37    protected ImpsAddress() {
38    }
39
40    protected ImpsAddress(String user, String resource, String domain) {
41        // build the full address and unify the fields to lower case since imps
42        // address is case insensitive.
43        StringBuilder buf = new StringBuilder(ImpsConstants.ADDRESS_PREFIX);
44        if(user != null) {
45            buf.append(user.toLowerCase());
46        }
47        if(resource != null) {
48            buf.append('/').append(resource.toLowerCase());
49        }
50        if(domain != null) {
51            buf.append('@').append(domain.toLowerCase());
52        }
53        mFullAddress = buf.toString();
54        mUser = user;
55        mResource = resource;
56        mDomain = domain;
57        verifyAddress();
58    }
59
60    protected ImpsAddress(String full){
61        this(full, true);
62    }
63
64    protected ImpsAddress(String full, boolean verify) {
65        if (full == null || full.length() == 0) {
66            throw new IllegalArgumentException();
67        }
68
69        //TODO: escape reserved characters.
70        if(!full.startsWith(ImpsConstants.ADDRESS_PREFIX)) {
71            full = ImpsConstants.ADDRESS_PREFIX + full;
72        }
73
74        parse(full);
75        mFullAddress = full;
76
77        if (verify) {
78            verifyAddress();
79        }
80    }
81
82    private void parse(String full) {
83        mUser = parseUser(full);
84        mResource = parseResource(full);
85        mDomain = parseDomain(full);
86    }
87
88    private void verifyAddress() throws IllegalArgumentException {
89        ImpsLog.log("verifyAddress:" + mUser + ", " + mResource + ",  " + mDomain);
90        if(mUser == null && mResource == null) {
91            throw new IllegalArgumentException();
92        }
93
94        if(mUser != null) {
95            if(mUser.length() == 0) {
96                throw new IllegalArgumentException("Invalid user");
97            }
98            if(mUser.charAt(0) == '+') {//mobile number
99                for(int i = 1; i < mUser.length(); i++) {
100                    if(!Character.isDigit(mUser.charAt(i))) {
101                        throw new IllegalArgumentException("Invalid user");
102                    }
103                }
104            } else if(!isAlphaSequence(mUser)) {
105                throw new IllegalArgumentException("Invalid user");
106            }
107        }
108
109        if(mResource != null && !isAlphaSequence(mResource)) {
110            throw new IllegalArgumentException("Invalid resource");
111        }
112        if(mDomain != null && !isAlphaSequence(mDomain)) {
113            throw new IllegalArgumentException("Invalid domain");
114        }
115    }
116
117    /**
118     * Gets the User-part of the address which refers to the identification of
119     * the IMPS user.
120     *
121     * @return the User-part of the address.
122     */
123    public String getUser() {
124        return mUser;
125    }
126
127    /**
128     * Gets the Resource-part of the address which identifies the referred
129     * public or private resource.
130     *
131     * @return the Resource-part of the address.
132     */
133    public String getResource() {
134        return mResource;
135    }
136
137    /**
138     * Gets the Domain-part of the address which identifies the IMPS server
139     * domain.
140     *
141     * @return the Domain-part of the address.
142     */
143    public String getDomain() {
144        return mDomain;
145    }
146
147    /**
148     * Gets the full string representation of the address.
149     */
150    @Override
151    public String getFullName() {
152        return mFullAddress;
153    }
154
155    @Override
156    public void writeToParcel(Parcel dest) {
157        dest.writeString(mFullAddress);
158    }
159
160    @Override
161    public void readFromParcel(Parcel source) {
162        mFullAddress = source.readString();
163        parse(mFullAddress);
164    }
165
166    @Override
167    public boolean equals(Object other) {
168        return other instanceof ImpsAddress
169                && mFullAddress.equalsIgnoreCase(((ImpsAddress)other).mFullAddress);
170    }
171
172    @Override
173    public int hashCode() {
174        return mFullAddress.toLowerCase().hashCode();
175    }
176
177    /**
178     * Formats the address to a PrimitiveElement which can be used as the
179     * content of Sender or Recipient.
180     *
181     * @return a PrimitiveElement.
182     */
183    public abstract PrimitiveElement toPrimitiveElement();
184
185    /**
186     * Gets the entity this address object refers to.
187     *
188     * @param connection
189     * @return the entity this address refers to or <code>null</code> if the
190     *         entity not found.
191     */
192    abstract ImEntity getEntity(ImpsConnection connection);
193
194    /**
195     * Constructs an ImpsAddress from the Sender or Recipient element in a
196     * primitive.
197     *
198     * @param elem the Sender of Recipient element.
199     * @return the ImpsAddress object or <code>null</code> if it's not a valid
200     *         element.
201     */
202    public static ImpsAddress fromPrimitiveElement(PrimitiveElement elem) {
203        String type = elem.getTagName();
204        if(ImpsTags.User.equals(type)) {
205            return new ImpsUserAddress(elem.getChildContents(ImpsTags.UserID), false);
206        } else if(ImpsTags.Group.equals(type)) {
207            PrimitiveElement child = elem.getFirstChild();
208            if(child == null) {
209                throw new IllegalArgumentException();
210            }
211            if(ImpsTags.GroupID.equals(child.getTagName())){
212                return new ImpsGroupAddress(child.getContents());
213            } else {
214                String screeName = child.getChildContents(ImpsTags.SName);
215                String groupId = child.getChildContents(ImpsTags.GroupID);
216                return new ImpsGroupAddress(groupId, screeName);
217            }
218        } else if(ImpsTags.ContactList.equals(type)) {
219            return new ImpsContactListAddress(elem.getContents(), false);
220        } else {
221            throw new IllegalArgumentException();
222        }
223    }
224
225    private String parseUser(String full) {
226        int index = full.indexOf('/');
227        if(index == 3) {
228            return null;
229        }
230        if (index == -1) {
231            index = full.lastIndexOf('@');
232        }
233        if (index == -1) {
234            index = full.length();
235        }
236        return full.substring(3, index);
237    }
238
239    private String parseResource(String full) {
240        int beginIndex = full.indexOf('/');
241        if (beginIndex == -1) {
242            return null;
243        }
244        int endIndex = full.lastIndexOf('@');
245        if (endIndex == -1 || endIndex < beginIndex) {
246            endIndex = full.length();
247        }
248        return full.substring(beginIndex + 1, endIndex);
249    }
250
251    private String parseDomain(String full) {
252        int beginIndex = full.lastIndexOf('@');
253        return beginIndex == -1 ? null : full.substring(beginIndex + 1);
254    }
255
256    private boolean isAlphaSequence(String str) {
257        for(int i = 0; i < str.length(); i++) {
258            char ch = str.charAt(i);
259            if(ch < 32 || ch > 126 || isSpecial(ch)) {
260                return false;
261            }
262        }
263        return true;
264    }
265
266    private boolean isSpecial(char ch) {
267        for (char element : SPECIALS) {
268            if(ch == element) {
269                return true;
270            }
271        }
272        return false;
273    }
274}
275