Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.komusubi.feeder.sns.twitter; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import org.apache.commons.lang3.StringUtils; import org.komusubi.common.util.Resolver; import org.komusubi.feeder.model.AbstractScript; import org.komusubi.feeder.model.Message; import org.komusubi.feeder.model.Message.Script; import org.komusubi.feeder.model.ScriptLine; import com.twitter.Extractor; import com.twitter.Extractor.Entity; /** * @author jun.ozeki */ public class TweetMessage extends ArrayList<Script> implements Message { /** * * @author jun.ozeki */ public interface Fragment { String get(); } /** * * @author jun.ozeki */ public static class TimestampFragment implements Fragment, Serializable { private static final long serialVersionUID = 1L; private static final Provider<Date> DEFAULT_DATE_PROVIDER = new Provider<Date>() { @Override public Date get() { return new Date(); } }; private SimpleDateFormat formatter; private transient Provider<Date> provider; public TimestampFragment(@Named("fragment format") String fragmentFormat) { this(fragmentFormat, DEFAULT_DATE_PROVIDER); } public TimestampFragment(String fragmentFormat, Provider<Date> provider) { if (StringUtils.isEmpty(fragmentFormat)) throw new IllegalArgumentException("argument fragmentFormat MUST not be blank"); this.formatter = new SimpleDateFormat(fragmentFormat); if (provider != null) this.provider = provider; else this.provider = DEFAULT_DATE_PROVIDER; } @Deprecated // ignore argument "resolver" because does not need this method. public TimestampFragment(String fragmentFormat, Resolver<Date> resolver) { if (fragmentFormat == null || fragmentFormat.length() == 0) throw new IllegalArgumentException("arguemnt fragmentFormat MUST not be blank"); this.formatter = new SimpleDateFormat(fragmentFormat); this.provider = DEFAULT_DATE_PROVIDER; } /** * @see org.komusubi.feeder.sns.twitter.TweetMessage.Fragment#get() */ @Override public String get() { return formatter.format(provider.get()); } } /** * * @author jun.ozeki */ public static class TweetScript extends AbstractScript { private static final long serialVersionUID = 1L; private static final int MESSAGE_LENGTH_MAX = 140; private StringBuilder line; private String fragment; /** * create new instance. * * @param line */ public TweetScript(String line) { this(null, line); } /** * create new instance * * @param fragment * @param line */ public TweetScript(Fragment fragment, String line) { if (line == null) throw new Twitter4jException("line must NOT be null"); if (line.codePointCount(0, line.length()) > MESSAGE_LENGTH_MAX) { int length = line.codePointCount(0, line.length()); throw new Twitter4jException("over max length of line: " + length); } if (fragment != null) { this.fragment = fragment.get() + "\n"; // length check again. int length = line.codePointCount(0, line.length()) + this.fragment.codePointCount(0, this.fragment.length()); if (length > MESSAGE_LENGTH_MAX) { throw new Twitter4jException("over max length of line and fragment: " + length); } this.line = new StringBuilder(this.fragment); this.line.append(line); } else { this.line = new StringBuilder(line); } } public TweetScript append(String buffer) { if (this.lengthAfterTweeted() + lengthAfterTweeted(buffer) > TweetScript.MESSAGE_LENGTH_MAX) { throw new Twitter4jException(String.format("max length(%d) over(now:%d append length:%d): %s ", TweetScript.MESSAGE_LENGTH_MAX, this.lengthAfterTweeted(), lengthAfterTweeted(buffer), buffer)); } line.append(buffer); return this; } @Override public String fragment() { return this.fragment; } @Override public boolean isFragment() { return fragment != null; } public int lengthAfterTweeted() { return lengthAfterTweeted(line()); } public static int lengthAfterTweeted(String text) { if (text == null || text.isEmpty()) return 0; Extractor extractor = new Extractor(); List<Entity> entities = extractor.extractURLsWithIndices(text); // TODO t.co shorten url length is size 1 longer than bit.ly one. // (2013.10.15 now.) // if you want correctly attribute value, // refer to // https://dev.twitter.com/docs/api/1.1/get/help/configuration return text.codePointCount(0, text.length()) + entities.size(); } @Override public int codePointCount() { return codePointCount(0, line.length()); } public int codePointCount(int start, int end) { if (line == null) return 0; return line.codePointCount(start, end); } @Override public String codePointSubstring(int begin) { // FIXME consider codepoint if (begin > line.length()) throw new StringIndexOutOfBoundsException( "wrong index size: argument is " + begin + " but actual " + line.length()); return line.substring(begin); } @Override public String codePointSubstring(int begin, int end) { throw new UnsupportedOperationException("not implemented."); } @Override public String line() { return line.toString(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("TweetScript [line=").append(line).append("]"); return builder.toString(); } } private static final long serialVersionUID = 1L; private Fragment fragment; /** * create new instance. */ public TweetMessage() { this(null); } /** * create new instance. default constructor. */ @Inject public TweetMessage(Fragment fragment) { this.fragment = fragment; } /** * @see org.komusubi.feeder.model.Message#append(org.komusubi.feeder.model.Message.Script) */ @Override public Message append(Script script) { append(script.line()); return this; } @Override public boolean addAll(Collection<? extends Script> c) { for (Script s : c) add(s); return true; } @Override public boolean add(Script script) { if (script instanceof TweetScript) { super.add(script); } else { String line = script.line(); // FIXME code point count if (TweetScript.lengthAfterTweeted(line) > TweetScript.MESSAGE_LENGTH_MAX) { // lengthAfterTweeted size different from script.codePointCount by url shortening. // adjust position when match max length. int adjust = script.codePointCount() == TweetScript.MESSAGE_LENGTH_MAX ? 1 : 0; // edit value to 140 character for (int position = TweetScript.MESSAGE_LENGTH_MAX - adjust; position >= 0; position--) { if (line.codePointAt(position) != '\n') continue; super.add(new TweetScript(fragment, line.substring(0, position))); this.add(new ScriptLine(line.substring(position + 1))); // call recursively. return true; } // not found line feed '\n' int offset = 0; for (; TweetScript.lengthAfterTweeted(line.substring( offset)) > TweetScript.MESSAGE_LENGTH_MAX; offset += TweetScript.MESSAGE_LENGTH_MAX) { super.add(new TweetScript(fragment, line.substring(offset, TweetScript.MESSAGE_LENGTH_MAX))); } if (line.length() - offset > 0) super.add(new TweetScript(fragment, line.substring(offset))); } else { super.add(script); } } return true; } /** * @see org.komusubi.feeder.model.Message#append(java.lang.String) */ @Override public Message append(String line) { if (line == null) throw new Twitter4jException("line must NOT be null"); // line over max size if (TweetScript.lengthAfterTweeted(line) > TweetScript.MESSAGE_LENGTH_MAX) { // FIXME consider code point and word wrap. int offset = 0; for (; TweetScript.lengthAfterTweeted(line.substring( offset)) > TweetScript.MESSAGE_LENGTH_MAX; offset += TweetScript.MESSAGE_LENGTH_MAX) { super.add(new TweetScript(fragment, line.substring(offset, TweetScript.MESSAGE_LENGTH_MAX))); } // append remain if (line.length() - offset > 0) { super.add(new TweetScript(fragment, line.substring(offset))); } } else { // try append latest script object. if (size() > 0) { AbstractScript latest = (AbstractScript) get(size() - 1); if (TweetScript.lengthAfterTweeted(latest.line()) + TweetScript.lengthAfterTweeted(line) <= TweetScript.MESSAGE_LENGTH_MAX) { latest.append(line); } else { super.add(new TweetScript(fragment, line)); } } else { super.add(new TweetScript(fragment, line)); } } return this; } /** * @see org.komusubi.feeder.model.Message#text() */ @Override public String text() { StringBuilder builder = new StringBuilder(); for (Script script : this) { builder.append(script.line()); } return builder.toString(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("TweetMessage [fragment=").append(fragment).append("]"); builder.append(super.toString()); return builder.toString(); } }