com.nttec.everychan.ui.presentation.PresentationModel.java Source code

Java tutorial

Introduction

Here is the source code for com.nttec.everychan.ui.presentation.PresentationModel.java

Source

/*
 * Everychan Android (Meta Imageboard Client)
 * Copyright (C) 2014-2016  miku-nyan <https://github.com/miku-nyan>
 *     
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nttec.everychan.ui.presentation;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.lang3.tuple.Triple;

import com.nttec.everychan.api.interfaces.CancellableTask;
import com.nttec.everychan.api.models.AttachmentModel;
import com.nttec.everychan.api.models.PostModel;
import com.nttec.everychan.api.models.ThreadModel;
import com.nttec.everychan.api.models.UrlPageModel;
import com.nttec.everychan.api.util.ChanModels;
import com.nttec.everychan.cache.SerializablePage;
import com.nttec.everychan.common.Logger;
import com.nttec.everychan.common.MainApplication;
import com.nttec.everychan.lib.org_json.JSONArray;
import com.nttec.everychan.ui.Database;
import com.nttec.everychan.ui.Database.IsHiddenDelegate;
import com.nttec.everychan.ui.presentation.ClickableURLSpan.URLSpanClickListener;
import com.nttec.everychan.ui.presentation.FlowTextHelper.FloatingModel;
import com.nttec.everychan.ui.presentation.HtmlParser.ImageGetter;
import com.nttec.everychan.ui.settings.AutohideActivity;
import com.nttec.everychan.ui.theme.ThemeUtils;
import android.content.res.Resources.Theme;

/**
 *  - ?   ( ?? ?)  ?.<br>
 * ?     {@link PresentationItemModel} - {@link #presentationList} ??  ? ? ?.
 * @author miku-nyan
 *
 */
public class PresentationModel {
    private static final String TAG = "PresentationModel";

    /** ?? ? -  {@link SerializablePage} */
    public final SerializablePage source;
    /**  ?? URL  ?, ??   ?,   ??  e-mail (mailto) */
    public final URLSpanClickListener spanClickListener;
    /**  , ???  ? ? (, ?) */
    public final ImageGetter imageGetter;
    private final Theme theme;
    private FloatingModel[] floatingModels;
    private final DateFormat dateFormat;
    private final boolean reduceNames;
    private final IsHiddenDelegate isHiddenDelegate;
    private final List<AutohideActivity.CompiledAutohideRule> autohideRules;

    /**
     *      ?  .
     * ?? , ???   -  ? .
     * ? ? ?,    LRU ?  ?  ,
     * ?  ? {@link #PresentationModel(PresentationModel)}
     */
    public final int size;

    /**
     * ?     ?   .
     * ?  (?)  ??  {@link #updateViewModels()}  ?,
     *    ?? (?)  {@link #source}
     */
    public List<PresentationItemModel> presentationList = null;

    private ArrayList<Triple<AttachmentModel, String, String>> attachments = null;
    private Object lock = new Object();

    private HashMap<String, Integer> postNumbersMap = new HashMap<String, Integer>();

    private volatile boolean notReady;

    /**
     * ?
     * @param source ?? ? -  {@link SerializablePage}
     * @param localTime ?,   ???  ?  (? true),  ? ??  (? false)
     * @param reduceNames ? true,  ?   (. ?)   ??
     * @param spanClickListener  ?? URL  ?, ??   ?,   ??  e-mail (mailto)
     * @param imageGetter  , ???  ? ? (, ?)
     * @param theme ? 
     * @param floatingModels ??    ?  ?.  ? - ? 
     * ,  - ? ? ?     (gif, , ). 
     * ?  null, ?    .
     */
    public PresentationModel(SerializablePage source, boolean localTime, boolean reduceNames,
            URLSpanClickListener spanClickListener, ImageGetter imageGetter, Theme theme,
            FloatingModel[] floatingModels) {
        if (source.pageModel.type == UrlPageModel.TYPE_OTHERPAGE)
            throw new IllegalArgumentException();
        this.source = source;
        this.spanClickListener = spanClickListener;
        this.imageGetter = imageGetter;
        this.theme = theme;
        this.floatingModels = floatingModels;
        this.reduceNames = reduceNames;
        Database database = MainApplication.getInstance().database;
        this.isHiddenDelegate = source.pageModel.type == UrlPageModel.TYPE_THREADPAGE
                ? database.getCachedIsHiddenDelegate(source.pageModel.chanName, source.pageModel.boardName,
                        source.pageModel.threadNumber)
                : database.getDefaultIsHiddenDelegate();
        this.autohideRules = new ArrayList<AutohideActivity.CompiledAutohideRule>();
        try {
            JSONArray autohideJson = new JSONArray(MainApplication.getInstance().settings.getAutohideRulesJson());
            for (int i = 0; i < autohideJson.length(); ++i) {
                AutohideActivity.AutohideRule rule = AutohideActivity.AutohideRule
                        .fromJson(autohideJson.getJSONObject(i));
                if (rule.matches(source.pageModel.chanName, source.pageModel.boardName,
                        source.pageModel.threadNumber)) {
                    this.autohideRules.add(new AutohideActivity.CompiledAutohideRule(rule));
                }
            }
        } catch (Exception e) {
            Logger.e(TAG, "error while processing regex autohide rules", e);
        }
        AndroidDateFormat.initPattern();
        String datePattern = AndroidDateFormat.getPattern();
        DateFormat dateFormat = datePattern == null
                ? DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
                : new SimpleDateFormat(datePattern, Locale.US);
        dateFormat.setTimeZone(
                localTime ? TimeZone.getDefault() : TimeZone.getTimeZone(source.boardModel.timeZoneId));
        this.dateFormat = dateFormat;
        this.size = getSerializablePageSize(source);
    }

    /**
     * ? ?    (?   ??), ?   {@link #size}
     * @param model ? 
     */
    public PresentationModel(PresentationModel model) {
        source = model.source;
        spanClickListener = model.spanClickListener;
        imageGetter = model.imageGetter;
        theme = model.theme;
        floatingModels = model.floatingModels;
        dateFormat = model.dateFormat;
        reduceNames = model.reduceNames;
        isHiddenDelegate = model.isHiddenDelegate;
        autohideRules = model.autohideRules;
        size = getSerializablePageSize(source);
        presentationList = model.presentationList;
        attachments = model.attachments;
        postNumbersMap = model.postNumbersMap;
        notReady = model.notReady;
    }

    public interface RebuildCallback {
        void onRebuild();
    }

    /**
     *     ?  ?  
     */
    private static int getSerializablePageSize(SerializablePage page) {
        int size = 32, noPresentationSize = 0;
        if (page.posts != null) {
            size += (12 + (page.posts.length * 4));
            for (PostModel post : page.posts)
                size += ChanModels.getPostModelSize(post);
        }
        if (page.threads != null) {
            size += (12 + (page.threads.length * 4));
            for (ThreadModel threadModel : page.threads) {
                size += (32
                        + (threadModel.threadNumber == null ? 0 : (40 + (threadModel.threadNumber.length() * 2))));
                size += (12 + (threadModel.posts.length * 4));
                if (threadModel.posts.length > 0)
                    size += ChanModels.getPostModelSize(threadModel.posts[0]);
                for (int i = 1; i < threadModel.posts.length; ++i)
                    noPresentationSize += ChanModels.getPostModelSize(threadModel.posts[i]);
            }
        }

        return size * 3 + noPresentationSize;
    }

    /**
     * ? ( ?)  
     * @param showIndex ? ? (  ?)   ?
     * @param task ?? 
     * @param rebuildCallback ? ()    ?, ? ?? ???
     */
    public synchronized void updateViewModels(boolean showIndex, CancellableTask task,
            RebuildCallback rebuildCallback) {
        PostModel[] posts = source.posts;
        if (posts == null) {
            posts = new PostModel[source.threads.length];
            for (int i = 0; i < source.threads.length; ++i) {
                posts[i] = source.threads[i].posts[0];
            }
        }
        try {
            updateViewModels(posts, showIndex, task, rebuildCallback);
        } catch (OutOfMemoryError oom) {
            MainApplication.freeMemory();
            Logger.e(TAG, oom);
            if (task != null && task.isCancelled())
                return;
            try {
                updateViewModels(posts, showIndex, task, rebuildCallback);
            } catch (OutOfMemoryError oom1) {
                MainApplication.freeMemory();
                Logger.e(TAG, oom1);
            }
        }
    }

    /**
     * ?   ?  ? ( ? ? ?  ? ?)
     * @param floatingModels ??    ?  ?
     */
    public void setFloatingModels(FloatingModel[] models) {
        if (!FlowTextHelper.IS_AVAILABLE)
            return;
        if (models == null || models.length != 2 || models[0] == null || models[1] == null)
            return;
        if (floatingModels == null || floatingModels.length != 2 || floatingModels[0] == null
                || floatingModels[1] == null)
            return;
        if (models[0].equals(floatingModels[0]) && models[1].equals(floatingModels[1]))
            return;

        try {
            this.floatingModels = models;
            int size = presentationList.size();
            for (int i = 0; i < size; ++i)
                presentationList.get(i).changeFloatingModels(models);
        } catch (Exception e) {
            Logger.e(TAG, e);
        }
    }

    private synchronized void updateViewModels(PostModel[] posts, boolean showIndex, CancellableTask task,
            RebuildCallback rebuildCallback) {
        if (task == null)
            task = CancellableTask.NOT_CANCELLABLE;
        synchronized (lock) {
            notReady = true;
        }
        if (presentationList == null) {
            presentationList = new ArrayList<PresentationItemModel>(posts.length); //   ? ?(?)
            attachments = new ArrayList<Triple<AttachmentModel, String, String>>();
        }

        if (task.isCancelled())
            return;

        String[] subscriptions = null;
        if (source.pageModel.type == UrlPageModel.TYPE_THREADPAGE
                && MainApplication.getInstance().settings.highlightSubscriptions()) {
            subscriptions = MainApplication.getInstance().subscriptions.getSubscriptions(source.pageModel.chanName,
                    source.pageModel.boardName, source.pageModel.threadNumber);
        }

        boolean headersRebuilding = false;
        int indexCounter = 0;

        boolean rebuild = false;
        if (posts.length < presentationList.size()) {
            rebuild = true;
            Logger.d(TAG, "rebuild: new list is shorter");
        } else {
            for (int i = 0, size = presentationList.size(); i < size; ++i) {
                if (!presentationList.get(i).sourceModel.number.equals(posts[i].number)
                        || ChanModels.hashPostModel(posts[i]) != presentationList.get(i).sourceModelHash) {
                    rebuild = true;
                    Logger.d(TAG, "rebuild: changed item " + i);
                    break;
                }
                if (showIndex) {
                    if (!posts[i].deleted)
                        ++indexCounter;
                    if (headersRebuilding |= presentationList.get(i).isDeleted != posts[i].deleted) {
                        presentationList.get(i).buildSpannedHeader(!posts[i].deleted ? indexCounter : -1,
                                source.boardModel.bumpLimit, reduceNames ? source.boardModel.defaultUserName : null,
                                source.pageModel.type == UrlPageModel.TYPE_SEARCHPAGE ? posts[i].parentThread
                                        : null,
                                subscriptions != null ? Arrays.binarySearch(subscriptions, posts[i].number) >= 0
                                        : false);
                    }
                }
                presentationList.get(i).isDeleted = posts[i].deleted;
            }
        }

        if (task.isCancelled())
            return;

        if (rebuild) {
            if (rebuildCallback != null)
                rebuildCallback.onRebuild();
            presentationList.clear();
            postNumbersMap.clear();
            attachments.clear();
            indexCounter = 0;
        }

        final boolean openSpoilers = MainApplication.getInstance().settings.openSpoilers();

        for (int i = presentationList.size(); i < posts.length; ++i) {
            if (task.isCancelled())
                return;
            PresentationItemModel model = new PresentationItemModel(posts[i], source.pageModel.chanName,
                    source.pageModel.boardName,
                    source.pageModel.type == UrlPageModel.TYPE_THREADPAGE ? source.pageModel.threadNumber : null,
                    dateFormat, spanClickListener, imageGetter, ThemeUtils.ThemeColors.getInstance(theme),
                    openSpoilers, floatingModels, subscriptions);
            postNumbersMap.put(posts[i].number, i);
            if (source.pageModel.type == UrlPageModel.TYPE_THREADPAGE) {
                for (String ref : model.referencesTo) {
                    Integer postPosition = postNumbersMap.get(ref);
                    if (postPosition != null && postPosition < presentationList.size()) {
                        presentationList.get(postPosition).addReferenceFrom(model.sourceModel.number);
                    }
                }
            }
            presentationList.add(model);
            for (int j = 0; j < model.attachmentHashes.length; ++j) {
                attachments.add(Triple.of(posts[i].attachments[j], model.attachmentHashes[j], posts[i].number));
            }

            model.buildSpannedHeader(showIndex && !posts[i].deleted ? ++indexCounter : -1,
                    source.boardModel.bumpLimit, reduceNames ? source.boardModel.defaultUserName : null,
                    source.pageModel.type == UrlPageModel.TYPE_SEARCHPAGE ? posts[i].parentThread : null,
                    subscriptions != null ? Arrays.binarySearch(subscriptions, posts[i].number) >= 0 : false);

            if (source.pageModel.type == UrlPageModel.TYPE_THREADPAGE) {
                model.hidden = isHiddenDelegate.isHidden(source.pageModel.chanName, source.pageModel.boardName,
                        source.pageModel.threadNumber, posts[i].number);
            } else if (source.pageModel.type == UrlPageModel.TYPE_BOARDPAGE
                    || source.pageModel.type == UrlPageModel.TYPE_CATALOGPAGE) {
                model.hidden = isHiddenDelegate.isHidden(source.pageModel.chanName, source.pageModel.boardName,
                        posts[i].number, null);
            }
            if (!model.hidden && ( //?
            source.pageModel.type == UrlPageModel.TYPE_THREADPAGE
                    || source.pageModel.type == UrlPageModel.TYPE_BOARDPAGE
                    || source.pageModel.type == UrlPageModel.TYPE_CATALOGPAGE)) {
                for (AutohideActivity.CompiledAutohideRule rule : autohideRules) {
                    if ((rule.inComment && model.spannedComment != null
                            && rule.pattern.matcher(model.spannedComment).find())
                            || (rule.inSubject && posts[i].subject != null
                                    && rule.pattern.matcher(posts[i].subject).find())
                            || (rule.inName && (posts[i].name != null && rule.pattern.matcher(posts[i].name).find())
                                    || (posts[i].trip != null && rule.pattern.matcher(posts[i].trip).find()))) {
                        model.hidden = true;
                        model.autohideReason = rule.regex;
                    }
                }
            }
        }

        if (source.pageModel.type == UrlPageModel.TYPE_THREADPAGE) {
            for (PresentationItemModel model : presentationList) {
                if (task.isCancelled())
                    return;
                model.buildReferencesString();
            }
        }

        if (source.threads != null) {
            for (int i = 0; i < source.threads.length; ++i) {
                if (task.isCancelled())
                    return;
                presentationList.get(i).buildPostsCountString(source.threads[i].postsCount,
                        source.threads[i].attachmentsCount);
                presentationList.get(i).buildStickyClosedString(source.threads[i].isSticky,
                        source.threads[i].isClosed);
            }
        }
        notReady = false;
    }

    /**
     *  true, ? ?     (   ?   )
     */
    public boolean isNotReady() {
        return notReady;
    }

    /**
     * ?  notReady  true (,   ,    ? )
     */
    public void setNotReady() {
        notReady = true;
    }

    /**
     *  ??    ?,   {@link Triple}   ?, ? ?   ?,   ??? 
     * @return ??  null, ?   ?  ???   
     */
    public List<Triple<AttachmentModel, String, String>> getAttachments() {
        if (notReady || attachments == null)
            return null;
        synchronized (lock) {
            if (notReady)
                return null;
            return new ArrayList<Triple<AttachmentModel, String, String>>(attachments);
        }
    }

    /**
     *   ??  ? ({@link #presentationList}),  ?, ?     ?  
     * ( ? ?   null)
     */
    public List<PresentationItemModel> getSafePresentationList() {
        if (notReady || presentationList == null)
            return null;
        synchronized (lock) {
            if (notReady)
                return null;
            return new ArrayList<PresentationItemModel>(presentationList);
        }
    }

}