移除过时第三方库,同时把第三方库下载到本地,修改源代码适配最新的SDK后以模块的方式集成到项目内部
parent
5edc4493c0
commit
d185ba4e5f
@ -0,0 +1,22 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
defaultConfig {
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionName "1.4.5"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'com.zhy.autolayout'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/zhy/android/sdk/android-sdk-macosx/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@ -0,0 +1,15 @@
|
||||
package zhy.com.autolayout;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application>
|
||||
{
|
||||
public ApplicationTest()
|
||||
{
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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 com.zhy.autolayout;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import com.zhy.autolayout.utils.AutoLayoutHelper;
|
||||
|
||||
public class AutoFrameLayout extends FrameLayout
|
||||
{
|
||||
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
|
||||
|
||||
public AutoFrameLayout(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AutoFrameLayout(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public AutoFrameLayout(Context context, AttributeSet attrs, int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AutoFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutParams generateLayoutParams(AttributeSet attrs)
|
||||
{
|
||||
return new LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
if (!isInEditMode())
|
||||
{
|
||||
mHelper.adjustChildren();
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
|
||||
{
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public static class LayoutParams extends FrameLayout.LayoutParams
|
||||
implements AutoLayoutHelper.AutoLayoutParams
|
||||
{
|
||||
private AutoLayoutInfo mAutoLayoutInfo;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs)
|
||||
{
|
||||
super(c, attrs);
|
||||
|
||||
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height)
|
||||
{
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height, int gravity)
|
||||
{
|
||||
super(width, height, gravity);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(MarginLayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(FrameLayout.LayoutParams source)
|
||||
{
|
||||
super((MarginLayoutParams) source);
|
||||
gravity = source.gravity;
|
||||
}
|
||||
|
||||
public LayoutParams(LayoutParams source)
|
||||
{
|
||||
this((FrameLayout.LayoutParams) source);
|
||||
mAutoLayoutInfo = source.mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoLayoutInfo getAutoLayoutInfo()
|
||||
{
|
||||
return mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.zhy.autolayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
;import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/11/19.
|
||||
*/
|
||||
public class AutoLayoutActivity extends AppCompatActivity
|
||||
{
|
||||
private static final String LAYOUT_LINEARLAYOUT = "LinearLayout";
|
||||
private static final String LAYOUT_FRAMELAYOUT = "FrameLayout";
|
||||
private static final String LAYOUT_RELATIVELAYOUT = "RelativeLayout";
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(String name, Context context, AttributeSet attrs)
|
||||
{
|
||||
View view = null;
|
||||
if (name.equals(LAYOUT_FRAMELAYOUT))
|
||||
{
|
||||
view = new AutoFrameLayout(context, attrs);
|
||||
}
|
||||
|
||||
if (name.equals(LAYOUT_LINEARLAYOUT))
|
||||
{
|
||||
view = new AutoLinearLayout(context, attrs);
|
||||
}
|
||||
|
||||
if (name.equals(LAYOUT_RELATIVELAYOUT))
|
||||
{
|
||||
view = new AutoRelativeLayout(context, attrs);
|
||||
}
|
||||
|
||||
if (view != null) return view;
|
||||
|
||||
return super.onCreateView(name, context, attrs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
package com.zhy.autolayout;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zhy.autolayout.attr.Attrs;
|
||||
import com.zhy.autolayout.attr.AutoAttr;
|
||||
import com.zhy.autolayout.attr.HeightAttr;
|
||||
import com.zhy.autolayout.attr.MarginBottomAttr;
|
||||
import com.zhy.autolayout.attr.MarginLeftAttr;
|
||||
import com.zhy.autolayout.attr.MarginRightAttr;
|
||||
import com.zhy.autolayout.attr.MarginTopAttr;
|
||||
import com.zhy.autolayout.attr.MaxHeightAttr;
|
||||
import com.zhy.autolayout.attr.MaxWidthAttr;
|
||||
import com.zhy.autolayout.attr.MinHeightAttr;
|
||||
import com.zhy.autolayout.attr.MinWidthAttr;
|
||||
import com.zhy.autolayout.attr.PaddingBottomAttr;
|
||||
import com.zhy.autolayout.attr.PaddingLeftAttr;
|
||||
import com.zhy.autolayout.attr.PaddingRightAttr;
|
||||
import com.zhy.autolayout.attr.PaddingTopAttr;
|
||||
import com.zhy.autolayout.attr.TextSizeAttr;
|
||||
import com.zhy.autolayout.attr.WidthAttr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AutoLayoutInfo
|
||||
{
|
||||
private List<AutoAttr> autoAttrs = new ArrayList<>();
|
||||
|
||||
public void addAttr(AutoAttr autoAttr)
|
||||
{
|
||||
autoAttrs.add(autoAttr);
|
||||
}
|
||||
|
||||
|
||||
public void fillAttrs(View view)
|
||||
{
|
||||
for (AutoAttr autoAttr : autoAttrs)
|
||||
{
|
||||
autoAttr.apply(view);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static AutoLayoutInfo getAttrFromView(View view, int attrs, int base)
|
||||
{
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
if (params == null) return null;
|
||||
AutoLayoutInfo autoLayoutInfo = new AutoLayoutInfo();
|
||||
|
||||
// width & height
|
||||
if ((attrs & Attrs.WIDTH) != 0 && params.width > 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(WidthAttr.generate(params.width, base));
|
||||
}
|
||||
|
||||
if ((attrs & Attrs.HEIGHT) != 0 && params.height > 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(HeightAttr.generate(params.height, base));
|
||||
}
|
||||
|
||||
//margin
|
||||
if (params instanceof ViewGroup.MarginLayoutParams)
|
||||
{
|
||||
if ((attrs & Attrs.MARGIN) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginLeftAttr.generate(((ViewGroup.MarginLayoutParams) params).leftMargin, base));
|
||||
autoLayoutInfo.addAttr(MarginTopAttr.generate(((ViewGroup.MarginLayoutParams) params).topMargin, base));
|
||||
autoLayoutInfo.addAttr(MarginRightAttr.generate(((ViewGroup.MarginLayoutParams) params).rightMargin, base));
|
||||
autoLayoutInfo.addAttr(MarginBottomAttr.generate(((ViewGroup.MarginLayoutParams) params).bottomMargin, base));
|
||||
}
|
||||
if ((attrs & Attrs.MARGIN_LEFT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginLeftAttr.generate(((ViewGroup.MarginLayoutParams) params).leftMargin, base));
|
||||
}
|
||||
if ((attrs & Attrs.MARGIN_TOP) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginTopAttr.generate(((ViewGroup.MarginLayoutParams) params).topMargin, base));
|
||||
}
|
||||
if ((attrs & Attrs.MARGIN_RIGHT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginRightAttr.generate(((ViewGroup.MarginLayoutParams) params).rightMargin, base));
|
||||
}
|
||||
if ((attrs & Attrs.MARGIN_BOTTOM) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginBottomAttr.generate(((ViewGroup.MarginLayoutParams) params).bottomMargin, base));
|
||||
}
|
||||
}
|
||||
|
||||
//padding
|
||||
if ((attrs & Attrs.PADDING) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(PaddingLeftAttr.generate(view.getPaddingLeft(), base));
|
||||
autoLayoutInfo.addAttr(PaddingTopAttr.generate(view.getPaddingTop(), base));
|
||||
autoLayoutInfo.addAttr(PaddingRightAttr.generate(view.getPaddingRight(), base));
|
||||
autoLayoutInfo.addAttr(PaddingBottomAttr.generate(view.getPaddingBottom(), base));
|
||||
}
|
||||
if ((attrs & Attrs.PADDING_LEFT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginLeftAttr.generate(view.getPaddingLeft(), base));
|
||||
}
|
||||
if ((attrs & Attrs.PADDING_TOP) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginTopAttr.generate(view.getPaddingTop(), base));
|
||||
}
|
||||
if ((attrs & Attrs.PADDING_RIGHT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginRightAttr.generate(view.getPaddingRight(), base));
|
||||
}
|
||||
if ((attrs & Attrs.PADDING_BOTTOM) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MarginBottomAttr.generate(view.getPaddingBottom(), base));
|
||||
}
|
||||
|
||||
//minWidth ,maxWidth , minHeight , maxHeight
|
||||
if ((attrs & Attrs.MIN_WIDTH) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MinWidthAttr.generate(MinWidthAttr.getMinWidth(view), base));
|
||||
}
|
||||
if ((attrs & Attrs.MAX_WIDTH) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MaxWidthAttr.generate(MaxWidthAttr.getMaxWidth(view), base));
|
||||
}
|
||||
if ((attrs & Attrs.MIN_HEIGHT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MinHeightAttr.generate(MinHeightAttr.getMinHeight(view), base));
|
||||
}
|
||||
if ((attrs & Attrs.MAX_HEIGHT) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(MaxHeightAttr.generate(MaxHeightAttr.getMaxHeight(view), base));
|
||||
}
|
||||
|
||||
//textsize
|
||||
|
||||
if (view instanceof TextView)
|
||||
{
|
||||
if ((attrs & Attrs.TEXTSIZE) != 0)
|
||||
{
|
||||
autoLayoutInfo.addAttr(TextSizeAttr.generate((int) ((TextView) view).getTextSize(), base));
|
||||
}
|
||||
}
|
||||
return autoLayoutInfo;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "AutoLayoutInfo{" +
|
||||
"autoAttrs=" + autoAttrs +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package com.zhy.autolayout;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.zhy.autolayout.utils.AutoLayoutHelper;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/6/30.
|
||||
*/
|
||||
public class AutoLinearLayout extends LinearLayout
|
||||
{
|
||||
|
||||
private AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
|
||||
|
||||
public AutoLinearLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AutoLinearLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
if (!isInEditMode())
|
||||
mHelper.adjustChildren();
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b)
|
||||
{
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LayoutParams generateLayoutParams(AttributeSet attrs)
|
||||
{
|
||||
return new AutoLinearLayout.LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
|
||||
public static class LayoutParams extends LinearLayout.LayoutParams
|
||||
implements AutoLayoutHelper.AutoLayoutParams
|
||||
{
|
||||
private AutoLayoutInfo mAutoLayoutInfo;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs)
|
||||
{
|
||||
super(c, attrs);
|
||||
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoLayoutInfo getAutoLayoutInfo()
|
||||
{
|
||||
return mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
|
||||
public LayoutParams(int width, int height)
|
||||
{
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(MarginLayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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 com.zhy.autolayout;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.zhy.autolayout.utils.AutoLayoutHelper;
|
||||
|
||||
public class AutoRelativeLayout extends RelativeLayout
|
||||
{
|
||||
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
|
||||
|
||||
public AutoRelativeLayout(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AutoRelativeLayout(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public AutoRelativeLayout(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AutoRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutParams generateLayoutParams(AttributeSet attrs)
|
||||
{
|
||||
return new LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
if (!isInEditMode())
|
||||
mHelper.adjustChildren();
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
|
||||
{
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
|
||||
|
||||
public static class LayoutParams extends RelativeLayout.LayoutParams
|
||||
implements AutoLayoutHelper.AutoLayoutParams
|
||||
{
|
||||
private AutoLayoutInfo mAutoLayoutInfo;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs)
|
||||
{
|
||||
super(c, attrs);
|
||||
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height)
|
||||
{
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(MarginLayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoLayoutInfo getAutoLayoutInfo()
|
||||
{
|
||||
return mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
* <p/>
|
||||
* 与attrs.xml中数值对应
|
||||
*/
|
||||
public interface Attrs
|
||||
{
|
||||
public static final int WIDTH = 1;
|
||||
public static final int HEIGHT = WIDTH << 1;
|
||||
public static final int TEXTSIZE = HEIGHT << 1;
|
||||
public static final int PADDING = TEXTSIZE << 1;
|
||||
public static final int MARGIN = PADDING << 1;
|
||||
public static final int MARGIN_LEFT = MARGIN << 1;
|
||||
public static final int MARGIN_TOP = MARGIN_LEFT << 1;
|
||||
public static final int MARGIN_RIGHT = MARGIN_TOP << 1;
|
||||
public static final int MARGIN_BOTTOM = MARGIN_RIGHT << 1;
|
||||
public static final int PADDING_LEFT = MARGIN_BOTTOM << 1;
|
||||
public static final int PADDING_TOP = PADDING_LEFT << 1;
|
||||
public static final int PADDING_RIGHT = PADDING_TOP << 1;
|
||||
public static final int PADDING_BOTTOM = PADDING_RIGHT << 1;
|
||||
public static final int MIN_WIDTH = PADDING_BOTTOM << 1;
|
||||
public static final int MAX_WIDTH = MIN_WIDTH << 1;
|
||||
public static final int MIN_HEIGHT = MAX_WIDTH << 1;
|
||||
public static final int MAX_HEIGHT = MIN_HEIGHT << 1;
|
||||
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.zhy.autolayout.utils.AutoUtils;
|
||||
import com.zhy.autolayout.utils.L;
|
||||
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/4.
|
||||
*/
|
||||
public abstract class AutoAttr
|
||||
{
|
||||
public static final int BASE_WIDTH = 1;
|
||||
public static final int BASE_HEIGHT = 2;
|
||||
public static final int BASE_DEFAULT = 3;
|
||||
|
||||
protected int pxVal;
|
||||
protected int baseWidth;
|
||||
protected int baseHeight;
|
||||
|
||||
/*
|
||||
protected boolean isBaseWidth;
|
||||
protected boolean isBaseDefault;
|
||||
|
||||
public AutoAttr(int pxVal)
|
||||
{
|
||||
this.pxVal = pxVal;
|
||||
isBaseDefault = true;
|
||||
}
|
||||
|
||||
public AutoAttr(int pxVal, boolean isBaseWidth)
|
||||
{
|
||||
this.pxVal = pxVal;
|
||||
this.isBaseWidth = isBaseWidth;
|
||||
}
|
||||
*/
|
||||
|
||||
public AutoAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
this.pxVal = pxVal;
|
||||
this.baseWidth = baseWidth;
|
||||
this.baseHeight = baseHeight;
|
||||
}
|
||||
|
||||
public void apply(View view)
|
||||
{
|
||||
|
||||
boolean log = view.getTag() != null && view.getTag().toString().equals("auto");
|
||||
|
||||
if (log)
|
||||
{
|
||||
L.e(" pxVal = " + pxVal + " ," + this.getClass().getSimpleName());
|
||||
}
|
||||
int val;
|
||||
if (useDefault())
|
||||
{
|
||||
val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize();
|
||||
if (log)
|
||||
{
|
||||
L.e(" useDefault val= " + val);
|
||||
}
|
||||
} else if (baseWidth())
|
||||
{
|
||||
val = getPercentWidthSize();
|
||||
if (log)
|
||||
{
|
||||
L.e(" baseWidth val= " + val);
|
||||
}
|
||||
} else
|
||||
{
|
||||
val = getPercentHeightSize();
|
||||
if (log)
|
||||
{
|
||||
L.e(" baseHeight val= " + val);
|
||||
}
|
||||
}
|
||||
|
||||
if (val > 0)
|
||||
val = Math.max(val, 1);//for very thin divider
|
||||
execute(view, val);
|
||||
}
|
||||
|
||||
protected int getPercentWidthSize()
|
||||
{
|
||||
return AutoUtils.getPercentWidthSizeBigger(pxVal);
|
||||
}
|
||||
|
||||
protected int getPercentHeightSize()
|
||||
{
|
||||
return AutoUtils.getPercentHeightSizeBigger(pxVal);
|
||||
}
|
||||
|
||||
|
||||
protected boolean baseWidth()
|
||||
{
|
||||
return contains(baseWidth, attrVal());
|
||||
}
|
||||
|
||||
protected boolean useDefault()
|
||||
{
|
||||
return !contains(baseHeight, attrVal()) && !contains(baseWidth, attrVal());
|
||||
}
|
||||
|
||||
protected boolean contains(int baseVal, int flag)
|
||||
{
|
||||
return (baseVal & flag) != 0;
|
||||
}
|
||||
|
||||
protected abstract int attrVal();
|
||||
|
||||
protected abstract boolean defaultBaseWidth();
|
||||
|
||||
protected abstract void execute(View view, int val);
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "AutoAttr{" +
|
||||
"pxVal=" + pxVal +
|
||||
", baseWidth=" + baseWidth() +
|
||||
", defaultBaseWidth=" + defaultBaseWidth() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class HeightAttr extends AutoAttr
|
||||
{
|
||||
public HeightAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
ViewGroup.LayoutParams lp = view.getLayoutParams();
|
||||
lp.height = val;
|
||||
}
|
||||
|
||||
public static HeightAttr generate(int val, int baseFlag)
|
||||
{
|
||||
HeightAttr heightAttr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
heightAttr = new HeightAttr(val, Attrs.HEIGHT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
heightAttr = new HeightAttr(val, 0, Attrs.HEIGHT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
heightAttr = new HeightAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return heightAttr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class MarginAttr extends AutoAttr
|
||||
{
|
||||
public MarginAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MARGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(View view)
|
||||
{
|
||||
if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (useDefault())
|
||||
{
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.leftMargin = lp.rightMargin = getPercentWidthSize();
|
||||
lp.topMargin = lp.bottomMargin = getPercentHeightSize();
|
||||
return;
|
||||
}
|
||||
super.apply(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.leftMargin = lp.rightMargin = lp.topMargin = lp.bottomMargin = val;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class MarginBottomAttr extends AutoAttr
|
||||
{
|
||||
public MarginBottomAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MARGIN_BOTTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
if(!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
|
||||
{
|
||||
return ;
|
||||
}
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.bottomMargin = val;
|
||||
}
|
||||
|
||||
public static MarginBottomAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MarginBottomAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MarginBottomAttr(val, Attrs.MARGIN_BOTTOM, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MarginBottomAttr(val, 0, Attrs.MARGIN_BOTTOM);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MarginBottomAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class MarginLeftAttr extends AutoAttr
|
||||
{
|
||||
public MarginLeftAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MARGIN_LEFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.leftMargin = val;
|
||||
}
|
||||
|
||||
public static MarginLeftAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MarginLeftAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MarginLeftAttr(val, Attrs.MARGIN_LEFT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MarginLeftAttr(val, 0, Attrs.MARGIN_LEFT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MarginLeftAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class MarginRightAttr extends AutoAttr
|
||||
{
|
||||
public MarginRightAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MARGIN_RIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.rightMargin = val;
|
||||
}
|
||||
|
||||
|
||||
public static MarginRightAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MarginRightAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MarginRightAttr(val, Attrs.MARGIN_RIGHT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MarginRightAttr(val, 0, Attrs.MARGIN_RIGHT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MarginRightAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class MarginTopAttr extends AutoAttr
|
||||
{
|
||||
public MarginTopAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MARGIN_TOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
|
||||
lp.topMargin = val;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static MarginTopAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MarginTopAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MarginTopAttr(val, Attrs.MARGIN_TOP, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MarginTopAttr(val, 0, Attrs.MARGIN_TOP);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MarginTopAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/24.
|
||||
*/
|
||||
public class MaxHeightAttr extends AutoAttr
|
||||
{
|
||||
public MaxHeightAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MAX_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
try
|
||||
{
|
||||
Method setMaxWidthMethod = view.getClass().getMethod("setMaxHeight", int.class);
|
||||
setMaxWidthMethod.invoke(view, val);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static MaxHeightAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MaxHeightAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MaxHeightAttr(val, Attrs.MAX_HEIGHT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MaxHeightAttr(val, 0, Attrs.MAX_HEIGHT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MaxHeightAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
|
||||
public static int getMaxHeight(View view)
|
||||
{
|
||||
try
|
||||
{
|
||||
Method setMaxWidthMethod = view.getClass().getMethod("getMaxHeight");
|
||||
return (int) setMaxWidthMethod.invoke(view);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/24.
|
||||
*/
|
||||
public class MaxWidthAttr extends AutoAttr
|
||||
{
|
||||
public MaxWidthAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MAX_WIDTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
try
|
||||
{
|
||||
Method setMaxWidthMethod = view.getClass().getMethod("setMaxWidth", int.class);
|
||||
setMaxWidthMethod.invoke(view, val);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static MaxWidthAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MaxWidthAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MaxWidthAttr(val, Attrs.MAX_WIDTH, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MaxWidthAttr(val, 0, Attrs.MAX_WIDTH);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MaxWidthAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
|
||||
public static int getMaxWidth(View view)
|
||||
{
|
||||
try
|
||||
{
|
||||
Method setMaxWidthMethod = view.getClass().getMethod("getMaxWidth");
|
||||
return (int) setMaxWidthMethod.invoke(view);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/24.
|
||||
*/
|
||||
public class MinHeightAttr extends AutoAttr
|
||||
{
|
||||
public MinHeightAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MIN_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
try
|
||||
{
|
||||
view.setMinimumHeight(val);
|
||||
// Method setMaxWidthMethod = view.getClass().getMethod("setMinHeight", int.class);
|
||||
// setMaxWidthMethod.invoke(view, val);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static MinHeightAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MinHeightAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MinHeightAttr(val, Attrs.MIN_HEIGHT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MinHeightAttr(val, 0, Attrs.MIN_HEIGHT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MinHeightAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
|
||||
public static int getMinHeight(View view)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
{
|
||||
return view.getMinimumHeight();
|
||||
} else
|
||||
{
|
||||
try
|
||||
{
|
||||
Field minHeight = view.getClass().getField("mMinHeight");
|
||||
minHeight.setAccessible(true);
|
||||
return (int) minHeight.get(view);
|
||||
} catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/24.
|
||||
*/
|
||||
public class MinWidthAttr extends AutoAttr
|
||||
{
|
||||
public MinWidthAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.MIN_WIDTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Method setMaxWidthMethod = view.getClass().getMethod("setMinWidth", int.class);
|
||||
// setMaxWidthMethod.invoke(view, val);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
|
||||
view.setMinimumWidth(val);
|
||||
}
|
||||
|
||||
public static int getMinWidth(View view)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
return view.getMinimumWidth();
|
||||
try
|
||||
{
|
||||
Field minWidth = view.getClass().getField("mMinWidth");
|
||||
minWidth.setAccessible(true);
|
||||
return (int) minWidth.get(view);
|
||||
} catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public static MinWidthAttr generate(int val, int baseFlag)
|
||||
{
|
||||
MinWidthAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new MinWidthAttr(val, Attrs.MIN_WIDTH, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new MinWidthAttr(val, 0, Attrs.MIN_WIDTH);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new MinWidthAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class PaddingAttr extends AutoAttr
|
||||
{
|
||||
public PaddingAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.PADDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(View view)
|
||||
{
|
||||
int l, t, r, b;
|
||||
if (useDefault())
|
||||
{
|
||||
l = r = getPercentWidthSize();
|
||||
t = b = getPercentHeightSize();
|
||||
view.setPadding(l, t, r, b);
|
||||
return;
|
||||
}
|
||||
super.apply(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
view.setPadding(val, val, val, val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class PaddingBottomAttr extends AutoAttr
|
||||
{
|
||||
public PaddingBottomAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.PADDING_BOTTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
int l = view.getPaddingLeft();
|
||||
int t = view.getPaddingTop();
|
||||
int r = view.getPaddingRight();
|
||||
int b = val;
|
||||
view.setPadding(l, t, r, b);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static PaddingBottomAttr generate(int val, int baseFlag)
|
||||
{
|
||||
PaddingBottomAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new PaddingBottomAttr(val, Attrs.PADDING_BOTTOM, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new PaddingBottomAttr(val, 0, Attrs.PADDING_BOTTOM);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new PaddingBottomAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class PaddingLeftAttr extends AutoAttr
|
||||
{
|
||||
public PaddingLeftAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.PADDING_LEFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
int l = val;
|
||||
int t = view.getPaddingTop();
|
||||
int r = view.getPaddingRight();
|
||||
int b = view.getPaddingBottom();
|
||||
view.setPadding(l, t, r, b);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static PaddingLeftAttr generate(int val, int baseFlag)
|
||||
{
|
||||
PaddingLeftAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new PaddingLeftAttr(val, Attrs.PADDING_LEFT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new PaddingLeftAttr(val, 0, Attrs.PADDING_LEFT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new PaddingLeftAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class PaddingRightAttr extends AutoAttr
|
||||
{
|
||||
public PaddingRightAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.PADDING_RIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
int l = view.getPaddingLeft();
|
||||
int t = view.getPaddingTop();
|
||||
int r = val;
|
||||
int b = view.getPaddingBottom();
|
||||
view.setPadding(l, t, r, b);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static PaddingRightAttr generate(int val, int baseFlag)
|
||||
{
|
||||
PaddingRightAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new PaddingRightAttr(val, Attrs.PADDING_RIGHT, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new PaddingRightAttr(val, 0, Attrs.PADDING_RIGHT);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new PaddingRightAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class PaddingTopAttr extends AutoAttr
|
||||
{
|
||||
public PaddingTopAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.PADDING_TOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
int l = view.getPaddingLeft();
|
||||
int t = val;
|
||||
int r = view.getPaddingRight();
|
||||
int b = view.getPaddingBottom();
|
||||
view.setPadding(l, t, r, b);
|
||||
}
|
||||
|
||||
public static PaddingTopAttr generate(int val, int baseFlag)
|
||||
{
|
||||
PaddingTopAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new PaddingTopAttr(val, Attrs.PADDING_TOP, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new PaddingTopAttr(val, 0, Attrs.PADDING_TOP);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new PaddingTopAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/4.
|
||||
*/
|
||||
public class TextSizeAttr extends AutoAttr
|
||||
{
|
||||
|
||||
public TextSizeAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.TEXTSIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
if (!(view instanceof TextView))
|
||||
return;
|
||||
((TextView) view).setIncludeFontPadding(false);
|
||||
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, val);
|
||||
}
|
||||
|
||||
public static TextSizeAttr generate(int val, int baseFlag)
|
||||
{
|
||||
TextSizeAttr attr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
attr = new TextSizeAttr(val, Attrs.TEXTSIZE, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
attr = new TextSizeAttr(val, 0, Attrs.TEXTSIZE);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
attr = new TextSizeAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.zhy.autolayout.attr;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/5.
|
||||
*/
|
||||
public class WidthAttr extends AutoAttr
|
||||
{
|
||||
public WidthAttr(int pxVal, int baseWidth, int baseHeight)
|
||||
{
|
||||
super(pxVal, baseWidth, baseHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int attrVal()
|
||||
{
|
||||
return Attrs.WIDTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultBaseWidth()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(View view, int val)
|
||||
{
|
||||
ViewGroup.LayoutParams lp = view.getLayoutParams();
|
||||
lp.width = val;
|
||||
}
|
||||
|
||||
public static WidthAttr generate(int val, int baseFlag)
|
||||
{
|
||||
WidthAttr widthAttr = null;
|
||||
switch (baseFlag)
|
||||
{
|
||||
case AutoAttr.BASE_WIDTH:
|
||||
widthAttr = new WidthAttr(val, Attrs.WIDTH, 0);
|
||||
break;
|
||||
case AutoAttr.BASE_HEIGHT:
|
||||
widthAttr = new WidthAttr(val, 0, Attrs.WIDTH);
|
||||
break;
|
||||
case AutoAttr.BASE_DEFAULT:
|
||||
widthAttr = new WidthAttr(val, 0, 0);
|
||||
break;
|
||||
}
|
||||
return widthAttr;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package com.zhy.autolayout.config;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.zhy.autolayout.utils.L;
|
||||
import com.zhy.autolayout.utils.ScreenUtils;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/11/18.
|
||||
*/
|
||||
public class AutoLayoutConifg
|
||||
{
|
||||
|
||||
private static AutoLayoutConifg sIntance = new AutoLayoutConifg();
|
||||
|
||||
|
||||
private static final String KEY_DESIGN_WIDTH = "design_width";
|
||||
private static final String KEY_DESIGN_HEIGHT = "design_height";
|
||||
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
|
||||
private int mDesignWidth;
|
||||
private int mDesignHeight;
|
||||
|
||||
private boolean useDeviceSize;
|
||||
|
||||
|
||||
private AutoLayoutConifg()
|
||||
{
|
||||
}
|
||||
|
||||
public void checkParams()
|
||||
{
|
||||
if (mDesignHeight <= 0 || mDesignWidth <= 0)
|
||||
{
|
||||
throw new RuntimeException(
|
||||
"you must set " + KEY_DESIGN_WIDTH + " and " + KEY_DESIGN_HEIGHT + " in your manifest file.");
|
||||
}
|
||||
}
|
||||
|
||||
public AutoLayoutConifg useDeviceSize()
|
||||
{
|
||||
useDeviceSize = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static AutoLayoutConifg getInstance()
|
||||
{
|
||||
return sIntance;
|
||||
}
|
||||
|
||||
|
||||
public int getScreenWidth()
|
||||
{
|
||||
return mScreenWidth;
|
||||
}
|
||||
|
||||
public int getScreenHeight()
|
||||
{
|
||||
return mScreenHeight;
|
||||
}
|
||||
|
||||
public int getDesignWidth()
|
||||
{
|
||||
return mDesignWidth;
|
||||
}
|
||||
|
||||
public int getDesignHeight()
|
||||
{
|
||||
return mDesignHeight;
|
||||
}
|
||||
|
||||
|
||||
public void init(Context context)
|
||||
{
|
||||
getMetaData(context);
|
||||
|
||||
int[] screenSize = ScreenUtils.getScreenSize(context, useDeviceSize);
|
||||
mScreenWidth = screenSize[0];
|
||||
mScreenHeight = screenSize[1];
|
||||
L.e(" screenWidth =" + mScreenWidth + " ,screenHeight = " + mScreenHeight);
|
||||
}
|
||||
|
||||
private void getMetaData(Context context)
|
||||
{
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
ApplicationInfo applicationInfo;
|
||||
try
|
||||
{
|
||||
applicationInfo = packageManager.getApplicationInfo(context
|
||||
.getPackageName(), PackageManager.GET_META_DATA);
|
||||
if (applicationInfo != null && applicationInfo.metaData != null)
|
||||
{
|
||||
mDesignWidth = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH);
|
||||
mDesignHeight = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e)
|
||||
{
|
||||
throw new RuntimeException(
|
||||
"you must set " + KEY_DESIGN_WIDTH + " and " + KEY_DESIGN_HEIGHT + " in your manifest file.", e);
|
||||
}
|
||||
|
||||
L.e(" designWidth =" + mDesignWidth + " , designHeight = " + mDesignHeight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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 com.zhy.autolayout.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.zhy.autolayout.AutoLayoutInfo;
|
||||
import com.zhy.autolayout.R;
|
||||
import com.zhy.autolayout.attr.HeightAttr;
|
||||
import com.zhy.autolayout.attr.MarginAttr;
|
||||
import com.zhy.autolayout.attr.MarginBottomAttr;
|
||||
import com.zhy.autolayout.attr.MarginLeftAttr;
|
||||
import com.zhy.autolayout.attr.MarginRightAttr;
|
||||
import com.zhy.autolayout.attr.MarginTopAttr;
|
||||
import com.zhy.autolayout.attr.MaxHeightAttr;
|
||||
import com.zhy.autolayout.attr.MaxWidthAttr;
|
||||
import com.zhy.autolayout.attr.MinHeightAttr;
|
||||
import com.zhy.autolayout.attr.MinWidthAttr;
|
||||
import com.zhy.autolayout.attr.PaddingAttr;
|
||||
import com.zhy.autolayout.attr.PaddingBottomAttr;
|
||||
import com.zhy.autolayout.attr.PaddingLeftAttr;
|
||||
import com.zhy.autolayout.attr.PaddingRightAttr;
|
||||
import com.zhy.autolayout.attr.PaddingTopAttr;
|
||||
import com.zhy.autolayout.attr.TextSizeAttr;
|
||||
import com.zhy.autolayout.attr.WidthAttr;
|
||||
import com.zhy.autolayout.config.AutoLayoutConifg;
|
||||
|
||||
public class AutoLayoutHelper
|
||||
{
|
||||
private final ViewGroup mHost;
|
||||
|
||||
private static final int[] LL = new int[]
|
||||
{ //
|
||||
android.R.attr.textSize,
|
||||
android.R.attr.padding,//
|
||||
android.R.attr.paddingLeft,//
|
||||
android.R.attr.paddingTop,//
|
||||
android.R.attr.paddingRight,//
|
||||
android.R.attr.paddingBottom,//
|
||||
android.R.attr.layout_width,//
|
||||
android.R.attr.layout_height,//
|
||||
android.R.attr.layout_margin,//
|
||||
android.R.attr.layout_marginLeft,//
|
||||
android.R.attr.layout_marginTop,//
|
||||
android.R.attr.layout_marginRight,//
|
||||
android.R.attr.layout_marginBottom,//
|
||||
android.R.attr.maxWidth,//
|
||||
android.R.attr.maxHeight,//
|
||||
android.R.attr.minWidth,//
|
||||
android.R.attr.minHeight,//16843072
|
||||
|
||||
|
||||
};
|
||||
|
||||
private static final int INDEX_TEXT_SIZE = 0;
|
||||
private static final int INDEX_PADDING = 1;
|
||||
private static final int INDEX_PADDING_LEFT = 2;
|
||||
private static final int INDEX_PADDING_TOP = 3;
|
||||
private static final int INDEX_PADDING_RIGHT = 4;
|
||||
private static final int INDEX_PADDING_BOTTOM = 5;
|
||||
private static final int INDEX_WIDTH = 6;
|
||||
private static final int INDEX_HEIGHT = 7;
|
||||
private static final int INDEX_MARGIN = 8;
|
||||
private static final int INDEX_MARGIN_LEFT = 9;
|
||||
private static final int INDEX_MARGIN_TOP = 10;
|
||||
private static final int INDEX_MARGIN_RIGHT = 11;
|
||||
private static final int INDEX_MARGIN_BOTTOM = 12;
|
||||
private static final int INDEX_MAX_WIDTH = 13;
|
||||
private static final int INDEX_MAX_HEIGHT = 14;
|
||||
private static final int INDEX_MIN_WIDTH = 15;
|
||||
private static final int INDEX_MIN_HEIGHT = 16;
|
||||
|
||||
|
||||
/**
|
||||
* move to other place?
|
||||
*/
|
||||
private static AutoLayoutConifg mAutoLayoutConifg;
|
||||
|
||||
public AutoLayoutHelper(ViewGroup host)
|
||||
{
|
||||
mHost = host;
|
||||
|
||||
if (mAutoLayoutConifg == null)
|
||||
{
|
||||
initAutoLayoutConfig(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initAutoLayoutConfig(ViewGroup host)
|
||||
{
|
||||
mAutoLayoutConifg = AutoLayoutConifg.getInstance();
|
||||
mAutoLayoutConifg.init(host.getContext());
|
||||
}
|
||||
|
||||
|
||||
public void adjustChildren()
|
||||
{
|
||||
AutoLayoutConifg.getInstance().checkParams();
|
||||
|
||||
for (int i = 0, n = mHost.getChildCount(); i < n; i++)
|
||||
{
|
||||
View view = mHost.getChildAt(i);
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
|
||||
if (params instanceof AutoLayoutParams)
|
||||
{
|
||||
AutoLayoutInfo info =
|
||||
((AutoLayoutParams) params).getAutoLayoutInfo();
|
||||
if (info != null)
|
||||
{
|
||||
info.fillAttrs(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static AutoLayoutInfo getAutoLayoutInfo(Context context,
|
||||
AttributeSet attrs)
|
||||
{
|
||||
|
||||
AutoLayoutInfo info = new AutoLayoutInfo();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoLayout_Layout);
|
||||
int baseWidth = a.getInt(R.styleable.AutoLayout_Layout_layout_auto_basewidth, 0);
|
||||
int baseHeight = a.getInt(R.styleable.AutoLayout_Layout_layout_auto_baseheight, 0);
|
||||
a.recycle();
|
||||
|
||||
TypedArray array = context.obtainStyledAttributes(attrs, LL);
|
||||
|
||||
int n = array.getIndexCount();
|
||||
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int index = array.getIndex(i);
|
||||
// String val = array.getString(index);
|
||||
// if (!isPxVal(val)) continue;
|
||||
|
||||
if (!DimenUtils.isPxVal(array.peekValue(index))) continue;
|
||||
|
||||
int pxVal = 0;
|
||||
try
|
||||
{
|
||||
pxVal = array.getDimensionPixelOffset(index, 0);
|
||||
} catch (Exception ignore)//not dimension
|
||||
{
|
||||
continue;
|
||||
}
|
||||
switch (index)
|
||||
{
|
||||
case INDEX_TEXT_SIZE:
|
||||
info.addAttr(new TextSizeAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_PADDING:
|
||||
info.addAttr(new PaddingAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_PADDING_LEFT:
|
||||
info.addAttr(new PaddingLeftAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_PADDING_TOP:
|
||||
info.addAttr(new PaddingTopAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_PADDING_RIGHT:
|
||||
info.addAttr(new PaddingRightAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_PADDING_BOTTOM:
|
||||
info.addAttr(new PaddingBottomAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_WIDTH:
|
||||
info.addAttr(new WidthAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_HEIGHT:
|
||||
info.addAttr(new HeightAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MARGIN:
|
||||
info.addAttr(new MarginAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MARGIN_LEFT:
|
||||
info.addAttr(new MarginLeftAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MARGIN_TOP:
|
||||
info.addAttr(new MarginTopAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MARGIN_RIGHT:
|
||||
info.addAttr(new MarginRightAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MARGIN_BOTTOM:
|
||||
info.addAttr(new MarginBottomAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MAX_WIDTH:
|
||||
info.addAttr(new MaxWidthAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MAX_HEIGHT:
|
||||
info.addAttr(new MaxHeightAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MIN_WIDTH:
|
||||
info.addAttr(new MinWidthAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
case INDEX_MIN_HEIGHT:
|
||||
info.addAttr(new MinHeightAttr(pxVal, baseWidth, baseHeight));
|
||||
break;
|
||||
}
|
||||
}
|
||||
array.recycle();
|
||||
L.e(" getAutoLayoutInfo " + info.toString());
|
||||
return info;
|
||||
}
|
||||
|
||||
public interface AutoLayoutParams
|
||||
{
|
||||
AutoLayoutInfo getAutoLayoutInfo();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.zhy.autolayout.utils;
|
||||
|
||||
import android.util.TypedValue;
|
||||
|
||||
/**
|
||||
* Created by zhy on 16/3/3.
|
||||
*/
|
||||
public class DimenUtils
|
||||
{
|
||||
private static int getComplexUnit(int data)
|
||||
{
|
||||
return TypedValue.COMPLEX_UNIT_MASK & (data >> TypedValue.COMPLEX_UNIT_SHIFT);
|
||||
}
|
||||
|
||||
public static boolean isPxVal(TypedValue val)
|
||||
{
|
||||
if (val != null && val.type == TypedValue.TYPE_DIMENSION &&
|
||||
getComplexUnit(val.data) == TypedValue.COMPLEX_UNIT_PX)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.zhy.autolayout.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/11/18.
|
||||
*/
|
||||
public class L
|
||||
{
|
||||
public static boolean debug = false;
|
||||
private static final String TAG = "AUTO_LAYOUT";
|
||||
|
||||
public static void e(String msg)
|
||||
{
|
||||
if (debug)
|
||||
{
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.zhy.autolayout.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/4.<br/>
|
||||
* form http://stackoverflow.com/questions/1016896/get-screen-dimensions-in-pixels/15699681#15699681
|
||||
*/
|
||||
public class ScreenUtils
|
||||
{
|
||||
|
||||
public static int getStatusBarHeight(Context context)
|
||||
{
|
||||
int result = 0;
|
||||
try
|
||||
{
|
||||
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0)
|
||||
{
|
||||
result = context.getResources().getDimensionPixelSize(resourceId);
|
||||
}
|
||||
} catch (Resources.NotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static int[] getScreenSize(Context context, boolean useDeviceSize)
|
||||
{
|
||||
|
||||
int[] size = new int[2];
|
||||
|
||||
WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display d = w.getDefaultDisplay();
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
d.getMetrics(metrics);
|
||||
// since SDK_INT = 1;
|
||||
int widthPixels = metrics.widthPixels;
|
||||
int heightPixels = metrics.heightPixels;
|
||||
|
||||
if (!useDeviceSize)
|
||||
{
|
||||
size[0] = widthPixels;
|
||||
size[1] = heightPixels - getStatusBarHeight(context);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// includes window decorations (statusbar bar/menu bar)
|
||||
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17)
|
||||
try
|
||||
{
|
||||
widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d);
|
||||
heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d);
|
||||
} catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
// includes window decorations (statusbar bar/menu bar)
|
||||
if (Build.VERSION.SDK_INT >= 17)
|
||||
try
|
||||
{
|
||||
Point realSize = new Point();
|
||||
Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize);
|
||||
widthPixels = realSize.x;
|
||||
heightPixels = realSize.y;
|
||||
} catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
size[0] = widthPixels;
|
||||
size[1] = heightPixels;
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
package com.zhy.autolayout.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.zhy.autolayout.AutoLayoutInfo;
|
||||
import com.zhy.autolayout.R;
|
||||
import com.zhy.autolayout.utils.AutoLayoutHelper;
|
||||
import com.zhy.autolayout.utils.AutoUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by zhy on 15/12/10.
|
||||
*
|
||||
* //do not use
|
||||
*/
|
||||
public class MetroLayout extends ViewGroup
|
||||
{
|
||||
|
||||
private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this);
|
||||
|
||||
private static class MetroBlock
|
||||
{
|
||||
int left;
|
||||
int top;
|
||||
int width;
|
||||
}
|
||||
|
||||
private List<MetroBlock> mAvailablePos = new ArrayList<>();
|
||||
private int mDivider;
|
||||
|
||||
public MetroLayout(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MetroLayout);
|
||||
mDivider = a.getDimensionPixelOffset(R.styleable.MetroLayout_metro_divider, 0);
|
||||
mDivider = AutoUtils.getPercentWidthSizeBigger(mDivider);
|
||||
a.recycle();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
|
||||
if (true)
|
||||
randomColor();
|
||||
|
||||
if (!isInEditMode())
|
||||
mHelper.adjustChildren();
|
||||
|
||||
measureChildren(widthMeasureSpec, heightMeasureSpec);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
}
|
||||
|
||||
private void randomColor()
|
||||
{
|
||||
Random r = new Random(255);
|
||||
|
||||
for (int i = 0, n = getChildCount(); i < n; i++)
|
||||
{
|
||||
View v = getChildAt(i);
|
||||
|
||||
v.setBackgroundColor(Color.argb(100, r.nextInt(), r.nextInt(), r.nextInt()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b)
|
||||
{
|
||||
|
||||
initAvailablePosition();
|
||||
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int divider = mDivider;
|
||||
|
||||
for (int i = 0, n = getChildCount(); i < n; i++)
|
||||
{
|
||||
View v = getChildAt(i);
|
||||
if (v.getVisibility() == View.GONE) continue;
|
||||
|
||||
MetroBlock newPos = findAvailablePos(v);
|
||||
left = newPos.left;
|
||||
top = newPos.top;
|
||||
|
||||
int childWidth = v.getMeasuredWidth();
|
||||
int childHeight = v.getMeasuredHeight();
|
||||
|
||||
int right = left + childWidth;
|
||||
int bottom = top + childHeight;
|
||||
|
||||
v.layout(left, top, right, bottom);
|
||||
|
||||
if (childWidth + divider < newPos.width)
|
||||
{
|
||||
newPos.left += childWidth + divider;
|
||||
newPos.width -= childWidth + divider;
|
||||
} else
|
||||
{
|
||||
mAvailablePos.remove(newPos);
|
||||
}
|
||||
|
||||
MetroBlock p = new MetroBlock();
|
||||
p.left = left;
|
||||
p.top = bottom + divider;
|
||||
p.width = childWidth;
|
||||
mAvailablePos.add(p);
|
||||
|
||||
mergeAvailablePosition();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeAvailablePosition()
|
||||
{
|
||||
if (mAvailablePos.size() <= 1) return;
|
||||
|
||||
List<MetroBlock> needRemoveBlocks = new ArrayList<>();
|
||||
|
||||
MetroBlock one = mAvailablePos.get(0);
|
||||
MetroBlock two = mAvailablePos.get(1);
|
||||
|
||||
for (int i = 1, n = mAvailablePos.size(); i < n - 1; i++)
|
||||
{
|
||||
if (one.top == two.top)
|
||||
{
|
||||
one.width = one.width + two.width;
|
||||
needRemoveBlocks.add(one);
|
||||
two.left = one.left;
|
||||
two = mAvailablePos.get(i + 1);
|
||||
} else
|
||||
{
|
||||
one = mAvailablePos.get(i);
|
||||
two = mAvailablePos.get(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
mAvailablePos.removeAll(needRemoveBlocks);
|
||||
|
||||
}
|
||||
|
||||
private void initAvailablePosition()
|
||||
{
|
||||
mAvailablePos.clear();
|
||||
MetroBlock first = new MetroBlock();
|
||||
first.left = getPaddingLeft();
|
||||
first.top = getPaddingTop();
|
||||
first.width = getMeasuredWidth();
|
||||
mAvailablePos.add(first);
|
||||
}
|
||||
|
||||
private MetroBlock findAvailablePos(View view)
|
||||
{
|
||||
MetroBlock p = new MetroBlock();
|
||||
if (mAvailablePos.size() == 0)
|
||||
{
|
||||
p.left = getPaddingLeft();
|
||||
p.top = getPaddingTop();
|
||||
p.width = getMeasuredWidth();
|
||||
return p;
|
||||
}
|
||||
int min = mAvailablePos.get(0).top;
|
||||
MetroBlock minHeightPos = mAvailablePos.get(0);
|
||||
for (MetroBlock _p : mAvailablePos)
|
||||
{
|
||||
if (_p.top < min)
|
||||
{
|
||||
min = _p.top;
|
||||
minHeightPos = _p;
|
||||
}
|
||||
}
|
||||
return minHeightPos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MetroLayout.LayoutParams generateLayoutParams(AttributeSet attrs)
|
||||
{
|
||||
return new LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
public static class LayoutParams extends ViewGroup.MarginLayoutParams
|
||||
implements AutoLayoutHelper.AutoLayoutParams
|
||||
{
|
||||
private AutoLayoutInfo mAutoLayoutInfo;
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs)
|
||||
{
|
||||
super(c, attrs);
|
||||
mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height)
|
||||
{
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(MarginLayoutParams source)
|
||||
{
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(LayoutParams source)
|
||||
{
|
||||
this((ViewGroup.LayoutParams) source);
|
||||
mAutoLayoutInfo = source.mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoLayoutInfo getAutoLayoutInfo()
|
||||
{
|
||||
return mAutoLayoutInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="AutoLayout_Layout">
|
||||
|
||||
<attr name="layout_auto_basewidth">
|
||||
<flag name="width" value="1"></flag>
|
||||
<flag name="height" value="2"></flag>
|
||||
<flag name="textSize" value="4"></flag>
|
||||
<flag name="padding" value="8"></flag>
|
||||
<flag name="margin" value="16"></flag>
|
||||
<flag name="marginLeft" value="32"></flag>
|
||||
<flag name="marginTop" value="64"></flag>
|
||||
<flag name="marginRight" value="128"></flag>
|
||||
<flag name="marginBottom" value="256"></flag>
|
||||
<flag name="paddingLeft" value="512"></flag>
|
||||
<flag name="paddingTop" value="1024"></flag>
|
||||
<flag name="paddingRight" value="2048"></flag>
|
||||
<flag name="paddingBottom" value="4096"></flag>
|
||||
</attr>
|
||||
|
||||
<attr name="layout_auto_baseheight">
|
||||
<flag name="width" value="1"></flag>
|
||||
<flag name="height" value="2"></flag>
|
||||
<flag name="textSize" value="4"></flag>
|
||||
<flag name="padding" value="8"></flag>
|
||||
<flag name="margin" value="16"></flag>
|
||||
<flag name="marginLeft" value="32"></flag>
|
||||
<flag name="marginTop" value="64"></flag>
|
||||
<flag name="marginRight" value="128"></flag>
|
||||
<flag name="marginBottom" value="256"></flag>
|
||||
<flag name="paddingLeft" value="512"></flag>
|
||||
<flag name="paddingTop" value="1024"></flag>
|
||||
<flag name="paddingRight" value="2048"></flag>
|
||||
<flag name="paddingBottom" value="4096"></flag>
|
||||
<flag name="minWidth" value="8192"></flag>
|
||||
<flag name="maxWidth" value="16384"></flag>
|
||||
<flag name="minHeight" value="32768"></flag>
|
||||
<flag name="maxHeight" value="65536"></flag>
|
||||
</attr>
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
<declare-styleable name="MetroLayout">
|
||||
<attr name="metro_divider" format="dimension"></attr>
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
</resources>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="id_tag_autolayout_size" type="id"></item>
|
||||
<item name="id_tag_autolayout_padding" type="id"></item>
|
||||
<item name="id_tag_autolayout_margin" type="id"></item>
|
||||
</resources>
|
||||
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">autolayout</string>
|
||||
</resources>
|
||||
@ -0,0 +1,18 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
defaultConfig {
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionName "1.5.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'org.litepal'
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/tony/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@ -0,0 +1,13 @@
|
||||
package org.litepal;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.litepal.parser.LitePalAttr;
|
||||
import org.litepal.parser.LitePalConfig;
|
||||
import org.litepal.parser.LitePalParser;
|
||||
import org.litepal.tablemanager.Connector;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.Const;
|
||||
import org.litepal.util.SharedUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* LitePal is an Android library that allows developers to use SQLite database extremely easy.
|
||||
* You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to
|
||||
* work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()}
|
||||
* methods.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.4
|
||||
*/
|
||||
public class LitePal {
|
||||
|
||||
private static Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* Initialize to make LitePal ready to work. If you didn't configure LitePalApplication
|
||||
* in the AndroidManifest.xml, make sure you call this method as soon as possible. In
|
||||
* Application's onCreate() method will be fine.
|
||||
*
|
||||
* @param context
|
||||
* Application context.
|
||||
*/
|
||||
public static void initialize(Context context) {
|
||||
LitePalApplication.sContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a writable SQLiteDatabase.
|
||||
*
|
||||
* @return A writable SQLiteDatabase instance
|
||||
*/
|
||||
public static SQLiteDatabase getDatabase() {
|
||||
return Connector.getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main thread handler. You don't need this method. It's used by framework only.
|
||||
* @return Main thread handler.
|
||||
*/
|
||||
public static Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the using database to the one specified by parameter.
|
||||
* @param litePalDB
|
||||
* The database to switch to.
|
||||
*/
|
||||
public static void use(LitePalDB litePalDB) {
|
||||
LitePalAttr litePalAttr = LitePalAttr.getInstance();
|
||||
litePalAttr.setDbName(litePalDB.getDbName());
|
||||
litePalAttr.setVersion(litePalDB.getVersion());
|
||||
litePalAttr.setStorage(litePalDB.isExternalStorage() ? "external" : "internal");
|
||||
litePalAttr.setClassNames(litePalDB.getClassNames());
|
||||
// set the extra key name only when use database other than default or litepal.xml not exists
|
||||
if (!isDefaultDatabase(litePalDB.getDbName())) {
|
||||
litePalAttr.setExtraKeyName(litePalDB.getDbName());
|
||||
litePalAttr.setCases("lower");
|
||||
}
|
||||
Connector.clearLitePalOpenHelperInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the using database to default with configuration by litepal.xml.
|
||||
*/
|
||||
public static void useDefault() {
|
||||
LitePalAttr.clearInstance();
|
||||
Connector.clearLitePalOpenHelperInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified database.
|
||||
* @param dbName
|
||||
* Name of database to delete.
|
||||
* @return True if delete success, false otherwise.
|
||||
*/
|
||||
public static boolean deleteDatabase(String dbName) {
|
||||
if (!TextUtils.isEmpty(dbName)) {
|
||||
if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
|
||||
dbName = dbName + Const.Config.DB_NAME_SUFFIX;
|
||||
}
|
||||
File dbFile = LitePalApplication.getContext().getDatabasePath(dbName);
|
||||
if (dbFile.exists()) {
|
||||
boolean result = dbFile.delete();
|
||||
if (result) {
|
||||
removeVersionInSharedPreferences(dbName);
|
||||
Connector.clearLitePalOpenHelperInstance();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
String path = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/";
|
||||
dbFile = new File(path + dbName);
|
||||
boolean result = dbFile.delete();
|
||||
if (result) {
|
||||
removeVersionInSharedPreferences(dbName);
|
||||
Connector.clearLitePalOpenHelperInstance();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the database version in SharedPreferences file.
|
||||
* @param dbName
|
||||
*/
|
||||
private static void removeVersionInSharedPreferences(String dbName) {
|
||||
if (isDefaultDatabase(dbName)) {
|
||||
SharedUtil.removeVersion(null);
|
||||
} else {
|
||||
SharedUtil.removeVersion(dbName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the dbName is default database or not. If it's same as dbName in litepal.xml, then it is
|
||||
* default database.
|
||||
* @param dbName
|
||||
* Name of database to check.
|
||||
* @return True if it's default database, false otherwise.
|
||||
*/
|
||||
private static boolean isDefaultDatabase(String dbName) {
|
||||
if (BaseUtility.isLitePalXMLExists()) {
|
||||
if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
|
||||
dbName = dbName + Const.Config.DB_NAME_SUFFIX;
|
||||
}
|
||||
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
|
||||
String defaultDbName = config.getDbName();
|
||||
if (!defaultDbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
|
||||
defaultDbName = defaultDbName + Const.Config.DB_NAME_SUFFIX;
|
||||
}
|
||||
return dbName.equalsIgnoreCase(defaultDbName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal;
|
||||
|
||||
import org.litepal.exceptions.GlobalException;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Base class of LitePal to make things easier when developers need to use
|
||||
* context. When you need context, just use
|
||||
* <b>LitePalApplication.getContext()</b>. To make this function work, you need
|
||||
* to configure your AndroidManifest.xml. Specifying
|
||||
* <b>"org.litepal.LitePalApplication"</b> as the application name in your
|
||||
* <application> tag to enable LitePal get the context. Of course if you
|
||||
* need to write your own Application class, LitePal can still live with that.
|
||||
* But just remember make your own Application class inherited from
|
||||
* LitePalApplication instead of inheriting from Application directly. This can
|
||||
* make all things work without side effects. <br>
|
||||
* Besides if you don't want use the above way, you can also call the LitePal.initialize(Context)
|
||||
* method to do the same job. Just remember call this method as early as possible, in Application's onCreate()
|
||||
* method will be fine.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.0
|
||||
*/
|
||||
public class LitePalApplication extends Application {
|
||||
|
||||
/**
|
||||
* Global application context.
|
||||
*/
|
||||
static Context sContext;
|
||||
|
||||
/**
|
||||
* Construct of LitePalApplication. Initialize application context.
|
||||
*/
|
||||
public LitePalApplication() {
|
||||
sContext = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Use {@link LitePal#initialize(Context)} instead.
|
||||
* @param context
|
||||
* Application context.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void initialize(Context context) {
|
||||
sContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global application context.
|
||||
*
|
||||
* @return Application context.
|
||||
* @throws org.litepal.exceptions.GlobalException
|
||||
*/
|
||||
public static Context getContext() {
|
||||
if (sContext == null) {
|
||||
throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL);
|
||||
}
|
||||
return sContext;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,693 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal;
|
||||
|
||||
import org.litepal.annotation.Column;
|
||||
import org.litepal.crud.DataSupport;
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.exceptions.DatabaseGenerateException;
|
||||
import org.litepal.parser.LitePalAttr;
|
||||
import org.litepal.tablemanager.model.AssociationsModel;
|
||||
import org.litepal.tablemanager.model.ColumnModel;
|
||||
import org.litepal.tablemanager.model.GenericModel;
|
||||
import org.litepal.tablemanager.model.TableModel;
|
||||
import org.litepal.tablemanager.typechange.BlobOrm;
|
||||
import org.litepal.tablemanager.typechange.BooleanOrm;
|
||||
import org.litepal.tablemanager.typechange.DateOrm;
|
||||
import org.litepal.tablemanager.typechange.DecimalOrm;
|
||||
import org.litepal.tablemanager.typechange.NumericOrm;
|
||||
import org.litepal.tablemanager.typechange.OrmChange;
|
||||
import org.litepal.tablemanager.typechange.TextOrm;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.Const;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Base class of all the LitePal components. If each component need to
|
||||
* interactive with other components or they have some same logic with duplicate
|
||||
* codes, LitePalBase may be the solution.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
public abstract class LitePalBase {
|
||||
|
||||
public static final String TAG = "LitePalBase";
|
||||
|
||||
/**
|
||||
* Action to get associations.
|
||||
*/
|
||||
private static final int GET_ASSOCIATIONS_ACTION = 1;
|
||||
|
||||
/**
|
||||
* Action to get association info.
|
||||
*/
|
||||
private static final int GET_ASSOCIATION_INFO_ACTION = 2;
|
||||
|
||||
/**
|
||||
* All the supporting mapping types currently in the array.
|
||||
*/
|
||||
private OrmChange[] typeChangeRules = { new NumericOrm(), new TextOrm(), new BooleanOrm(),
|
||||
new DecimalOrm(), new DateOrm(), new BlobOrm()};
|
||||
|
||||
/**
|
||||
* This is map of class name to fields list. Indicates that each class has which supported fields.
|
||||
*/
|
||||
private Map<String, List<Field>> classFieldsMap = new HashMap<String, List<Field>>();
|
||||
|
||||
/**
|
||||
* This is map of class name to generic fields list. Indicates that each class has which supported generic fields.
|
||||
*/
|
||||
private Map<String, List<Field>> classGenericFieldsMap = new HashMap<String, List<Field>>();
|
||||
|
||||
/**
|
||||
* The collection contains all association models.
|
||||
*/
|
||||
private Collection<AssociationsModel> mAssociationModels;
|
||||
|
||||
/**
|
||||
* The collection contains all association info.
|
||||
*/
|
||||
private Collection<AssociationsInfo> mAssociationInfos;
|
||||
|
||||
/**
|
||||
* The collection contains all generic models.
|
||||
*/
|
||||
private Collection<GenericModel> mGenericModels;
|
||||
|
||||
/**
|
||||
* This method is used to get the table model by the class name passed
|
||||
* in. The principle to generate table model is that each field in the class
|
||||
* with non-static modifier and has a type among int/Integer, long/Long,
|
||||
* short/Short, float/Float, double/Double, char/Character, boolean/Boolean
|
||||
* or String, would generate a column with same name as corresponding field.
|
||||
* If users don't want some of the fields map a column, declare an ignore
|
||||
* annotation with {@link Column#ignore()}.
|
||||
*
|
||||
* @param className
|
||||
* The full name of the class to map in database.
|
||||
* @return A table model with table name, class name and the map of column
|
||||
* name and column type.
|
||||
*/
|
||||
protected TableModel getTableModel(String className) {
|
||||
String tableName = DBUtility.getTableNameByClassName(className);
|
||||
TableModel tableModel = new TableModel();
|
||||
tableModel.setTableName(tableName);
|
||||
tableModel.setClassName(className);
|
||||
List<Field> supportedFields = getSupportedFields(className);
|
||||
for (Field field : supportedFields) {
|
||||
ColumnModel columnModel = convertFieldToColumnModel(field);
|
||||
tableModel.addColumnModel(columnModel);
|
||||
}
|
||||
return tableModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get association models depends on the given class
|
||||
* name list.
|
||||
*
|
||||
* @param classNames
|
||||
* The names of the classes that want to get their associations.
|
||||
* @return Collection of association models.
|
||||
*/
|
||||
protected Collection<AssociationsModel> getAssociations(List<String> classNames) {
|
||||
if (mAssociationModels == null) {
|
||||
mAssociationModels = new HashSet<AssociationsModel>();
|
||||
}
|
||||
if (mGenericModels == null) {
|
||||
mGenericModels = new HashSet<GenericModel>();
|
||||
}
|
||||
mAssociationModels.clear();
|
||||
mGenericModels.clear();
|
||||
for (String className : classNames) {
|
||||
analyzeClassFields(className, GET_ASSOCIATIONS_ACTION);
|
||||
}
|
||||
return mAssociationModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all generic models for create generic tables.
|
||||
* @return All generic models.
|
||||
*/
|
||||
protected Collection<GenericModel> getGenericModels() {
|
||||
return mGenericModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the association info model by the class name.
|
||||
*
|
||||
* @param className
|
||||
* The class name to introspection.
|
||||
* @return Collection of association info.
|
||||
*/
|
||||
protected Collection<AssociationsInfo> getAssociationInfo(String className) {
|
||||
if (mAssociationInfos == null) {
|
||||
mAssociationInfos = new HashSet<AssociationsInfo>();
|
||||
}
|
||||
mAssociationInfos.clear();
|
||||
analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION);
|
||||
return mAssociationInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the fields in the class. But not each field is supported to add
|
||||
* a column to the table. Only the basic data types and String are
|
||||
* supported. This method will intercept all the types which are not
|
||||
* supported and return a new list of supported fields.
|
||||
*
|
||||
* @param className
|
||||
* The full name of the class.
|
||||
* @return A list of supported fields.
|
||||
*/
|
||||
protected List<Field> getSupportedFields(String className) {
|
||||
List<Field> fieldList = classFieldsMap.get(className);
|
||||
if (fieldList == null) {
|
||||
List<Field> supportedFields = new ArrayList<Field>();
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
|
||||
}
|
||||
recursiveSupportedFields(clazz, supportedFields);
|
||||
classFieldsMap.put(className, supportedFields);
|
||||
return supportedFields;
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all supported generic fields in the class. Supporting rule is in {@link BaseUtility#isGenericTypeSupported(String)}.
|
||||
* @param className
|
||||
* The full name of the class.
|
||||
* @return A list of supported generic fields.
|
||||
*/
|
||||
protected List<Field> getSupportedGenericFields(String className) {
|
||||
List<Field> genericFieldList = classGenericFieldsMap.get(className);
|
||||
if (genericFieldList == null) {
|
||||
List<Field> supportedGenericFields = new ArrayList<Field>();
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
|
||||
}
|
||||
recursiveSupportedGenericFields(clazz, supportedGenericFields);
|
||||
classGenericFieldsMap.put(className, supportedGenericFields);
|
||||
return supportedGenericFields;
|
||||
}
|
||||
return genericFieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the field type implements from List or Set, regard it as a collection.
|
||||
*
|
||||
* @param fieldType
|
||||
* The field type.
|
||||
* @return True if the field type is collection, false otherwise.
|
||||
*/
|
||||
protected boolean isCollection(Class<?> fieldType) {
|
||||
return isList(fieldType) || isSet(fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the field type implements from List, regard it as a list.
|
||||
*
|
||||
* @param fieldType
|
||||
* The field type.
|
||||
* @return True if the field type is List, false otherwise.
|
||||
*/
|
||||
protected boolean isList(Class<?> fieldType) {
|
||||
return List.class.isAssignableFrom(fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the field type implements from Set, regard it as a set.
|
||||
*
|
||||
* @param fieldType
|
||||
* The field type.
|
||||
* @return True if the field type is Set, false otherwise.
|
||||
*/
|
||||
protected boolean isSet(Class<?> fieldType) {
|
||||
return Set.class.isAssignableFrom(fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge the passed in column is an id column or not. The column named id or
|
||||
* _id will be considered as id column.
|
||||
*
|
||||
* @param columnName
|
||||
* The name of column.
|
||||
* @return Return true if it's id column, otherwise return false.
|
||||
*/
|
||||
protected boolean isIdColumn(String columnName) {
|
||||
return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* If two tables are associated, one table have a foreign key column. The
|
||||
* foreign key column name will be the associated table name with _id
|
||||
* appended.
|
||||
*
|
||||
* @param associatedTableName
|
||||
* The associated table name.
|
||||
* @return The foreign key column name.
|
||||
*/
|
||||
protected String getForeignKeyColumnName(String associatedTableName) {
|
||||
return BaseUtility.changeCase(associatedTableName + "_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column type for creating table by field type.
|
||||
* @param fieldType
|
||||
* Type of field.
|
||||
* @return The column type for creating table.
|
||||
*/
|
||||
protected String getColumnType(String fieldType) {
|
||||
String columnType;
|
||||
for (OrmChange ormChange : typeChangeRules) {
|
||||
columnType = ormChange.object2Relation(fieldType);
|
||||
if (columnType != null) {
|
||||
return columnType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generic type class of List or Set. If there's no generic type of
|
||||
* List or Set return null.
|
||||
*
|
||||
* @param field
|
||||
* A generic type field.
|
||||
* @return The generic type of List or Set.
|
||||
*/
|
||||
protected Class<?> getGenericTypeClass(Field field) {
|
||||
Type genericType = field.getGenericType();
|
||||
if (genericType != null) {
|
||||
if (genericType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) genericType;
|
||||
return (Class<?>) parameterizedType.getActualTypeArguments()[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void recursiveSupportedFields(Class<?> clazz, List<Field> supportedFields) {
|
||||
if (clazz == DataSupport.class || clazz == Object.class) {
|
||||
return;
|
||||
}
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
if (fields != null && fields.length > 0) {
|
||||
for (Field field : fields) {
|
||||
Column annotation = field.getAnnotation(Column.class);
|
||||
if (annotation != null && annotation.ignore()) {
|
||||
continue;
|
||||
}
|
||||
int modifiers = field.getModifiers();
|
||||
if (!Modifier.isStatic(modifiers)) {
|
||||
Class<?> fieldTypeClass = field.getType();
|
||||
String fieldType = fieldTypeClass.getName();
|
||||
if (BaseUtility.isFieldTypeSupported(fieldType)) {
|
||||
supportedFields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recursiveSupportedFields(clazz.getSuperclass(), supportedFields);
|
||||
}
|
||||
|
||||
private void recursiveSupportedGenericFields(Class<?> clazz, List<Field> supportedGenericFields) {
|
||||
if (clazz == DataSupport.class || clazz == Object.class) {
|
||||
return;
|
||||
}
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
if (fields != null && fields.length > 0) {
|
||||
for (Field field : fields) {
|
||||
Column annotation = field.getAnnotation(Column.class);
|
||||
if (annotation != null && annotation.ignore()) {
|
||||
continue;
|
||||
}
|
||||
int modifiers = field.getModifiers();
|
||||
if (!Modifier.isStatic(modifiers) && isCollection(field.getType())) {
|
||||
String genericTypeName = getGenericTypeName(field);
|
||||
if (BaseUtility.isGenericTypeSupported(genericTypeName)) {
|
||||
supportedGenericFields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recursiveSupportedGenericFields(clazz.getSuperclass(), supportedGenericFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspection of the passed in class. Analyze the fields of current class
|
||||
* and find out the associations of it.
|
||||
*
|
||||
* @param className
|
||||
* The class name to introspection.
|
||||
* @param action
|
||||
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
|
||||
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
|
||||
*/
|
||||
private void analyzeClassFields(String className, int action) {
|
||||
try {
|
||||
Class<?> dynamicClass = Class.forName(className);
|
||||
Field[] fields = dynamicClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (isPrivateAndNonPrimitive(field)) {
|
||||
oneToAnyConditions(className, field, action);
|
||||
manyToAnyConditions(className, field, action);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge the field is a private non primitive field or not.
|
||||
*
|
||||
* @param field
|
||||
* The field to judge.
|
||||
* @return True if the field is <b>private</b> and <b>non primitive</b>,
|
||||
* false otherwise.
|
||||
*/
|
||||
private boolean isPrivateAndNonPrimitive(Field field) {
|
||||
return Modifier.isPrivate(field.getModifiers()) && !field.getType().isPrimitive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with one to any association conditions. e.g. Song and Album. An
|
||||
* album have many songs, and a song belongs to one album. So if there's an
|
||||
* Album model defined in Song with private modifier, and in Album there's a
|
||||
* List or Set with generic type of Song and declared as private modifier,
|
||||
* they are one2many association. If there's no List or Set defined in
|
||||
* Album, they will become one2one associations. If there's also a Song
|
||||
* model defined in Album with private modifier, maybe the album just have
|
||||
* one song, they are one2one association too.
|
||||
*
|
||||
* When it's many2one association, it's easy to just simply add a foreign id
|
||||
* column to the many side model's table. But when it comes to many2many
|
||||
* association, it can not be done without intermediate join table in
|
||||
* database. LitePal assumes that this join table's name is the
|
||||
* concatenation of the two target table names in alphabetical order.
|
||||
*
|
||||
* @param className
|
||||
* Source class name.
|
||||
* @param field
|
||||
* A field of source class.
|
||||
* @param action
|
||||
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
|
||||
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
private void oneToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
|
||||
Class<?> fieldTypeClass = field.getType();
|
||||
// If the mapping list contains the class name
|
||||
// defined in one class.
|
||||
if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName())) {
|
||||
Class<?> reverseDynamicClass = Class.forName(fieldTypeClass.getName());
|
||||
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
|
||||
// Look up if there's a reverse association
|
||||
// definition in the reverse class.
|
||||
boolean reverseAssociations = false;
|
||||
// Begin to check the fields of the defined
|
||||
// class.
|
||||
for (int i = 0; i < reverseFields.length; i++) {
|
||||
Field reverseField = reverseFields[i];
|
||||
if (!Modifier.isStatic(reverseField.getModifiers())) {
|
||||
Class<?> reverseFieldTypeClass = reverseField.getType();
|
||||
// If there's the from class name in the
|
||||
// defined class, they are one2one bidirectional
|
||||
// associations.
|
||||
if (className.equals(reverseFieldTypeClass.getName())) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
|
||||
fieldTypeClass.getName(), Const.Model.ONE_TO_ONE);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
|
||||
fieldTypeClass.getName(), field, reverseField, Const.Model.ONE_TO_ONE);
|
||||
}
|
||||
reverseAssociations = true;
|
||||
}
|
||||
// If there's the from class Set or List in
|
||||
// the defined class, they are many2one bidirectional
|
||||
// associations.
|
||||
else if (isCollection(reverseFieldTypeClass)) {
|
||||
String genericTypeName = getGenericTypeName(reverseField);
|
||||
if (className.equals(genericTypeName)) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
|
||||
className, Const.Model.MANY_TO_ONE);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
|
||||
className, field, reverseField, Const.Model.MANY_TO_ONE);
|
||||
}
|
||||
reverseAssociations = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's no from class in the defined class, they are
|
||||
// one2one unidirectional associations.
|
||||
if (!reverseAssociations) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, fieldTypeClass.getName(),
|
||||
fieldTypeClass.getName(), Const.Model.ONE_TO_ONE);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, fieldTypeClass.getName(),
|
||||
fieldTypeClass.getName(), field, null, Const.Model.ONE_TO_ONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with one to any association conditions. e.g. Song and Album. An
|
||||
* album have many songs, and a song belongs to one album. So if there's an
|
||||
* Album model defined in Song with private modifier, and in Album there's a
|
||||
* List or Set with generic type of Song and declared as private modifier,
|
||||
* they are one2many association. If there's no List or Set defined in
|
||||
* Album, they will become one2one associations. If there's also a Song
|
||||
* model defined in Album with private modifier, maybe the album just have
|
||||
* one song, they are one2one association too.
|
||||
*
|
||||
* When it's many2one association, it's easy to just simply add a foreign id
|
||||
* column to the many side model's table. But when it comes to many2many
|
||||
* association, it can not be done without intermediate join table in
|
||||
* database. LitePal assumes that this join table's name is the
|
||||
* concatenation of the two target table names in alphabetical order.
|
||||
*
|
||||
* @param className
|
||||
* Source class name.
|
||||
* @param field
|
||||
* A field of source class.
|
||||
* @param action
|
||||
* Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and
|
||||
* {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION}
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException {
|
||||
if (isCollection(field.getType())) {
|
||||
String genericTypeName = getGenericTypeName(field);
|
||||
// If the mapping list contains the genericTypeName, begin to check
|
||||
// this genericTypeName class.
|
||||
if (LitePalAttr.getInstance().getClassNames().contains(genericTypeName)) {
|
||||
Class<?> reverseDynamicClass = Class.forName(genericTypeName);
|
||||
Field[] reverseFields = reverseDynamicClass.getDeclaredFields();
|
||||
// Look up if there's a reverse association
|
||||
// definition in the reverse class.
|
||||
boolean reverseAssociations = false;
|
||||
for (int i = 0; i < reverseFields.length; i++) {
|
||||
Field reverseField = reverseFields[i];
|
||||
// Only map private fields
|
||||
if (!Modifier.isStatic(reverseField.getModifiers())) {
|
||||
Class<?> reverseFieldTypeClass = reverseField.getType();
|
||||
// If there's a from class name defined in the reverse
|
||||
// class, they are many2one bidirectional
|
||||
// associations.
|
||||
if (className.equals(reverseFieldTypeClass.getName())) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, genericTypeName,
|
||||
genericTypeName, Const.Model.MANY_TO_ONE);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName,
|
||||
field, reverseField, Const.Model.MANY_TO_ONE);
|
||||
}
|
||||
reverseAssociations = true;
|
||||
}
|
||||
// If there's a List or Set contains from class name
|
||||
// defined in the reverse class, they are many2many
|
||||
// association.
|
||||
else if (isCollection(reverseFieldTypeClass)) {
|
||||
String reverseGenericTypeName = getGenericTypeName(reverseField);
|
||||
if (className.equals(reverseGenericTypeName)) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, genericTypeName, null,
|
||||
Const.Model.MANY_TO_MANY);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, genericTypeName, null, field,
|
||||
reverseField, Const.Model.MANY_TO_MANY);
|
||||
}
|
||||
reverseAssociations = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// If there's no from class in the defined class, they
|
||||
// are many2one unidirectional associations.
|
||||
if (!reverseAssociations) {
|
||||
if (action == GET_ASSOCIATIONS_ACTION) {
|
||||
addIntoAssociationModelCollection(className, genericTypeName,
|
||||
genericTypeName, Const.Model.MANY_TO_ONE);
|
||||
} else if (action == GET_ASSOCIATION_INFO_ACTION) {
|
||||
addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName,
|
||||
field, null, Const.Model.MANY_TO_ONE);
|
||||
}
|
||||
}
|
||||
} else if(BaseUtility.isGenericTypeSupported(genericTypeName) && action == GET_ASSOCIATIONS_ACTION) {
|
||||
Column annotation = field.getAnnotation(Column.class);
|
||||
if (annotation != null && annotation.ignore()) {
|
||||
return;
|
||||
}
|
||||
GenericModel genericModel = new GenericModel();
|
||||
genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName()));
|
||||
genericModel.setValueColumnName(DBUtility.convertToValidColumnName(field.getName()));
|
||||
genericModel.setValueColumnType(getColumnType(genericTypeName));
|
||||
genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className));
|
||||
mGenericModels.add(genericModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Package a {@link org.litepal.tablemanager.model.AssociationsModel}, and add it into
|
||||
* {@link #mAssociationModels} Collection.
|
||||
*
|
||||
* @param className
|
||||
* The class name for {@link org.litepal.tablemanager.model.AssociationsModel}.
|
||||
* @param associatedClassName
|
||||
* The associated class name for {@link org.litepal.tablemanager.model.AssociationsModel}.
|
||||
* @param classHoldsForeignKey
|
||||
* The class which holds foreign key.
|
||||
* @param associationType
|
||||
* The association type for {@link org.litepal.tablemanager.model.AssociationsModel}.
|
||||
*/
|
||||
private void addIntoAssociationModelCollection(String className, String associatedClassName,
|
||||
String classHoldsForeignKey, int associationType) {
|
||||
AssociationsModel associationModel = new AssociationsModel();
|
||||
associationModel.setTableName(DBUtility.getTableNameByClassName(className));
|
||||
associationModel.setAssociatedTableName(DBUtility.getTableNameByClassName(associatedClassName));
|
||||
associationModel.setTableHoldsForeignKey(DBUtility.getTableNameByClassName(classHoldsForeignKey));
|
||||
associationModel.setAssociationType(associationType);
|
||||
mAssociationModels.add(associationModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package a {@link org.litepal.crud.model.AssociationsInfo}, and add it into
|
||||
* {@link #mAssociationInfos} Collection.
|
||||
*
|
||||
* @param selfClassName
|
||||
* The class name of self model.
|
||||
* @param associatedClassName
|
||||
* The class name of the class which associated with self class.
|
||||
* @param classHoldsForeignKey
|
||||
* The class which holds foreign key.
|
||||
* @param associateOtherModelFromSelf
|
||||
* The field of self class to declare has association with other
|
||||
* class.
|
||||
* @param associateSelfFromOtherModel
|
||||
* The field of the associated class to declare has association
|
||||
* with self class.
|
||||
* @param associationType
|
||||
* The association type.
|
||||
*/
|
||||
private void addIntoAssociationInfoCollection(String selfClassName, String associatedClassName,
|
||||
String classHoldsForeignKey, Field associateOtherModelFromSelf,
|
||||
Field associateSelfFromOtherModel, int associationType) {
|
||||
AssociationsInfo associationInfo = new AssociationsInfo();
|
||||
associationInfo.setSelfClassName(selfClassName);
|
||||
associationInfo.setAssociatedClassName(associatedClassName);
|
||||
associationInfo.setClassHoldsForeignKey(classHoldsForeignKey);
|
||||
associationInfo.setAssociateOtherModelFromSelf(associateOtherModelFromSelf);
|
||||
associationInfo.setAssociateSelfFromOtherModel(associateSelfFromOtherModel);
|
||||
associationInfo.setAssociationType(associationType);
|
||||
mAssociationInfos.add(associationInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generic type name of List or Set. If there's no generic type of
|
||||
* List or Set return null.
|
||||
*
|
||||
* @param field
|
||||
* A generic type field.
|
||||
* @return The name of generic type of List of Set.
|
||||
*/
|
||||
protected String getGenericTypeName(Field field) {
|
||||
Class<?> genericTypeClass = getGenericTypeClass(field);
|
||||
if (genericTypeClass != null) {
|
||||
return genericTypeClass.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a field instance into A ColumnModel instance. ColumnModel can provide information
|
||||
* when creating table.
|
||||
* @param field
|
||||
* A supported field to map into column.
|
||||
* @return ColumnModel instance contains column information.
|
||||
*/
|
||||
private ColumnModel convertFieldToColumnModel(Field field) {
|
||||
String fieldType = field.getType().getName();
|
||||
String columnType = getColumnType(fieldType);
|
||||
boolean nullable = true;
|
||||
boolean unique = false;
|
||||
String defaultValue = "";
|
||||
Column annotation = field.getAnnotation(Column.class);
|
||||
if (annotation != null) {
|
||||
nullable = annotation.nullable();
|
||||
unique = annotation.unique();
|
||||
defaultValue = annotation.defaultValue();
|
||||
}
|
||||
ColumnModel columnModel = new ColumnModel();
|
||||
columnModel.setColumnName(DBUtility.convertToValidColumnName(field.getName()));
|
||||
columnModel.setColumnType(columnType);
|
||||
columnModel.setNullable(nullable);
|
||||
columnModel.setUnique(unique);
|
||||
columnModel.setDefaultValue(defaultValue);
|
||||
return columnModel;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal;
|
||||
|
||||
import org.litepal.parser.LitePalAttr;
|
||||
import org.litepal.parser.LitePalConfig;
|
||||
import org.litepal.parser.LitePalParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration of LitePal database. It's similar to litepal.xml configuration, but allows to
|
||||
* configure database details at runtime. This is very important when comes to support multiple
|
||||
* databases functionality.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.4
|
||||
*/
|
||||
public class LitePalDB {
|
||||
|
||||
/**
|
||||
* The version of database.
|
||||
*/
|
||||
private int version;
|
||||
|
||||
/**
|
||||
* The name of database.
|
||||
*/
|
||||
private String dbName;
|
||||
|
||||
/**
|
||||
* Indicates that the database file stores in external storage or not.
|
||||
*/
|
||||
private boolean isExternalStorage = false;
|
||||
|
||||
/**
|
||||
* All the model classes that want to map in the database. Each class should
|
||||
* be given the full name including package name.
|
||||
*/
|
||||
private List<String> classNames;
|
||||
|
||||
/**
|
||||
* Construct a LitePalDB instance from the default configuration by litepal.xml. But database
|
||||
* name must be different than the default.
|
||||
* @param dbName
|
||||
* Name of database.
|
||||
* @return A LitePalDB instance which used the default configuration in litepal.xml but with a specified database name.
|
||||
*/
|
||||
public static LitePalDB fromDefault(String dbName) {
|
||||
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
|
||||
LitePalDB litePalDB = new LitePalDB(dbName, config.getVersion());
|
||||
litePalDB.setExternalStorage("external".equals(config.getStorage()));
|
||||
litePalDB.setClassNames(config.getClassNames());
|
||||
return litePalDB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a LitePalDB instance. Database name and version are necessary fields.
|
||||
* @param dbName
|
||||
* Name of database.
|
||||
* @param version
|
||||
* Version of database.
|
||||
*/
|
||||
public LitePalDB(String dbName, int version) {
|
||||
this.dbName = dbName;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getDbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
public boolean isExternalStorage() {
|
||||
return isExternalStorage;
|
||||
}
|
||||
|
||||
public void setExternalStorage(boolean isExternalStorage) {
|
||||
this.isExternalStorage = isExternalStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name list. Always add table_schema as a value.
|
||||
*
|
||||
* @return The class name list.
|
||||
*/
|
||||
public List<String> getClassNames() {
|
||||
if (classNames == null) {
|
||||
classNames = new ArrayList<String>();
|
||||
classNames.add("org.litepal.model.Table_Schema");
|
||||
} else if (classNames.isEmpty()) {
|
||||
classNames.add("org.litepal.model.Table_Schema");
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a class name into the current mapping model list.
|
||||
*
|
||||
* @param className
|
||||
* Full package class name.
|
||||
*/
|
||||
public void addClassName(String className) {
|
||||
getClassNames().add(className);
|
||||
}
|
||||
|
||||
void setClassNames(List<String> className) {
|
||||
this.classNames = className;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used for adding constraints to a column. Note that this annotation won't affect id column.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.3
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Column {
|
||||
|
||||
/**
|
||||
* Set nullable constraint for the column.
|
||||
*/
|
||||
boolean nullable() default true;
|
||||
|
||||
/**
|
||||
* Set unique constraint for the column.
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
/**
|
||||
* Set default value with String type for the column regardless of what column type is.
|
||||
*/
|
||||
String defaultValue() default "";
|
||||
|
||||
/**
|
||||
* Ignore to map this field into a column.
|
||||
*/
|
||||
boolean ignore() default false;
|
||||
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.litepal.LitePalBase;
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.exceptions.DataSupportException;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
/**
|
||||
* Base class of associations analyzer.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
abstract class AssociationsAnalyzer extends DataHandler {
|
||||
|
||||
/**
|
||||
* Get the associated models collection of associated model. Used for
|
||||
* reverse searching associations.
|
||||
*
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @param associationInfo
|
||||
* To get reverse associated models collection.
|
||||
* @return The associated models collection of associated model by analyzing
|
||||
* associationInfo.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Collection<DataSupport> getReverseAssociatedModels(DataSupport associatedModel,
|
||||
AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException,
|
||||
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return (Collection<DataSupport>) getFieldValue(associatedModel,
|
||||
associationInfo.getAssociateSelfFromOtherModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated models collection of associated model. Break quote of
|
||||
* source collection.
|
||||
*
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @param associationInfo
|
||||
* To get reverse associated models collection.
|
||||
* @param associatedModelCollection
|
||||
* The new associated models collection with same data as source
|
||||
* collection but different quote.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
protected void setReverseAssociatedModels(DataSupport associatedModel,
|
||||
AssociationsInfo associationInfo, Collection<DataSupport> associatedModelCollection)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(),
|
||||
associatedModelCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the associated model collection. If the associated model collection
|
||||
* is null, try to initialize the associated model collection by the given
|
||||
* associated field. If the associated field is subclass of List, make an
|
||||
* instance of ArrayList for associated model collection. If the associated
|
||||
* field is subclass of Set, make an instance of HashSet for associated
|
||||
* model collection. If the associated model collection is not null, doing
|
||||
* nothing.
|
||||
*
|
||||
* @param associatedModelCollection
|
||||
* The associated model collection to check null and initialize.
|
||||
* @param associatedField
|
||||
* The field to decide which type to initialize for associated
|
||||
* model collection.
|
||||
* @throws DataSupportException
|
||||
*/
|
||||
protected Collection<DataSupport> checkAssociatedModelCollection(
|
||||
Collection<DataSupport> associatedModelCollection, Field associatedField) {
|
||||
Collection<DataSupport> collection = null;
|
||||
if (isList(associatedField.getType())) {
|
||||
collection = new ArrayList<DataSupport>();
|
||||
} else if (isSet(associatedField.getType())) {
|
||||
collection = new HashSet<DataSupport>();
|
||||
} else {
|
||||
throw new DataSupportException(DataSupportException.WRONG_FIELD_TYPE_FOR_ASSOCIATIONS);
|
||||
}
|
||||
if (associatedModelCollection != null) {
|
||||
collection.addAll(associatedModelCollection);
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the bidirectional association by setting the baseObj instance to
|
||||
* the associated model.
|
||||
*
|
||||
* @param baseObj
|
||||
* The instance of self model.
|
||||
* @param associatedModel
|
||||
* The associated model.
|
||||
* @param associationInfo
|
||||
* The association info to get the association.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
protected void buildBidirectionalAssociations(DataSupport baseObj, DataSupport associatedModel,
|
||||
AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException,
|
||||
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(),
|
||||
baseObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the associated model is saved, add its' name and id to baseObj by
|
||||
* calling {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}. Or if
|
||||
* the baseObj is saved, add its' name and id to associated model by calling
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)}.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated model.
|
||||
*/
|
||||
protected void dealsAssociationsOnTheSideWithoutFK(DataSupport baseObj,
|
||||
DataSupport associatedModel) {
|
||||
if (associatedModel != null) {
|
||||
if (associatedModel.isSaved()) {
|
||||
baseObj.addAssociatedModelWithFK(associatedModel.getTableName(),
|
||||
associatedModel.getBaseObjId());
|
||||
} else {
|
||||
if (baseObj.isSaved()) {
|
||||
associatedModel.addAssociatedModelWithoutFK(baseObj.getTableName(),
|
||||
baseObj.getBaseObjId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the associated model of self model is null, the FK value in database
|
||||
* should be cleared if it exists when updating.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
*/
|
||||
protected void mightClearFKValue(DataSupport baseObj, AssociationsInfo associationInfo) {
|
||||
baseObj.addFKNameToClearSelf(getForeignKeyName(associationInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get foreign key name by {@link org.litepal.crud.model.AssociationsInfo}.
|
||||
*
|
||||
* @param associationInfo
|
||||
* To get foreign key name from.
|
||||
* @return The foreign key name.
|
||||
*/
|
||||
private String getForeignKeyName(AssociationsInfo associationInfo) {
|
||||
return getForeignKeyColumnName(DBUtility.getTableNameByClassName(associationInfo
|
||||
.getAssociatedClassName()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,914 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.litepal.LitePal;
|
||||
import org.litepal.crud.async.AverageExecutor;
|
||||
import org.litepal.crud.async.CountExecutor;
|
||||
import org.litepal.crud.async.FindExecutor;
|
||||
import org.litepal.crud.async.FindMultiExecutor;
|
||||
import org.litepal.tablemanager.Connector;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
/**
|
||||
* Allows developers to query tables with cluster style.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
public class ClusterQuery {
|
||||
|
||||
/**
|
||||
* Representing the selected columns in SQL.
|
||||
*/
|
||||
String[] mColumns;
|
||||
|
||||
/**
|
||||
* Representing the where clause in SQL.
|
||||
*/
|
||||
String[] mConditions;
|
||||
|
||||
/**
|
||||
* Representing the order by clause in SQL.
|
||||
*/
|
||||
String mOrderBy;
|
||||
|
||||
/**
|
||||
* Representing the limit clause in SQL.
|
||||
*/
|
||||
String mLimit;
|
||||
|
||||
/**
|
||||
* Representing the offset in SQL.
|
||||
*/
|
||||
String mOffset;
|
||||
|
||||
/**
|
||||
* Do not allow to create instance by developers.
|
||||
*/
|
||||
ClusterQuery() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Declaring to query which columns in table.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.select("name", "age").find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will find all rows with name and age columns in Person table.
|
||||
*
|
||||
* @param columns
|
||||
* A String array of which columns to return. Passing null will
|
||||
* return all columns.
|
||||
*
|
||||
* @return A ClusterQuery instance.
|
||||
*/
|
||||
public ClusterQuery select(String... columns) {
|
||||
mColumns = columns;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declaring to query which rows in table.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("name = ? or age > ?", "Tom", "14").find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will find rows which name is Tom or age greater than 14 in Person
|
||||
* table.
|
||||
*
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @return A ClusterQuery instance.
|
||||
*/
|
||||
public ClusterQuery where(String... conditions) {
|
||||
mConditions = conditions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declaring how to order the rows queried from table.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.order("name desc").find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will find all rows in Person table sorted by name with inverted
|
||||
* order.
|
||||
*
|
||||
* @param column
|
||||
* How to order the rows, formatted as an SQL ORDER BY clause.
|
||||
* Passing null will use the default sort order, which may be
|
||||
* unordered.
|
||||
* @return A ClusterQuery instance.
|
||||
*/
|
||||
public ClusterQuery order(String column) {
|
||||
mOrderBy = column;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the number of rows returned by the query.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.limit(2).find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will find the top 2 rows in Person table.
|
||||
*
|
||||
* @param value
|
||||
* Limits the number of rows returned by the query, formatted as
|
||||
* LIMIT clause.
|
||||
* @return A ClusterQuery instance.
|
||||
*/
|
||||
public ClusterQuery limit(int value) {
|
||||
mLimit = String.valueOf(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declaring the offset of rows returned by the query. This method must be
|
||||
* used with {@link #limit(int)}, or nothing will return.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.limit(1).offset(2).find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will find the third row in Person table.
|
||||
*
|
||||
* @param value
|
||||
* The offset amount of rows returned by the query.
|
||||
* @return A ClusterQuery instance.
|
||||
*/
|
||||
public ClusterQuery offset(int value) {
|
||||
mOffset = String.valueOf(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds multiple records by the cluster parameters. You can use the below
|
||||
* way to finish a complicated query:
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
|
||||
* .find(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also do the same job with SQLiteDatabase like this:
|
||||
*
|
||||
* <pre>
|
||||
* getSQLiteDatabase().query("Person", "name", "age > ?", new String[] { "14" }, null, null, "age",
|
||||
* "2,1");
|
||||
* </pre>
|
||||
*
|
||||
* Obviously, the first way is much more semantic.<br>
|
||||
* Note that the associated models won't be loaded by default considering
|
||||
* the efficiency, but you can do that by using
|
||||
* {@link org.litepal.crud.ClusterQuery#find(Class, boolean)}.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @return An object list with founded data from database, or an empty list.
|
||||
*/
|
||||
public <T> List<T> find(Class<T> modelClass) {
|
||||
return find(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #find(Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @return A FindMultiExecutor instance.
|
||||
*/
|
||||
public <T> FindMultiExecutor findAsync(final Class<T> modelClass) {
|
||||
return findAsync(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is mostly same as {@link org.litepal.crud.ClusterQuery#find(Class)} but an isEager
|
||||
* parameter. If set true the associated models will be loaded as well.
|
||||
* <br>
|
||||
* Note that isEager will only work for one deep level relation, considering the query efficiency.
|
||||
* You have to implement on your own if you need to load multiple deepness of relation at once.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object list with founded data from database, or an empty list.
|
||||
*/
|
||||
public synchronized <T> List<T> find(Class<T> modelClass, boolean isEager) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
String limit;
|
||||
if (mOffset == null) {
|
||||
limit = mLimit;
|
||||
} else {
|
||||
if (mLimit == null) {
|
||||
mLimit = "0";
|
||||
}
|
||||
limit = mOffset + "," + mLimit;
|
||||
}
|
||||
return queryHandler.onFind(modelClass, mColumns, mConditions, mOrderBy, limit, isEager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #find(Class, boolean)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return A FindMultiExecutor instance.
|
||||
*/
|
||||
public <T> FindMultiExecutor findAsync(final Class<T> modelClass, final boolean isEager) {
|
||||
final FindMultiExecutor executor = new FindMultiExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final List<T> t = find(modelClass, isEager);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first record by the cluster parameters. You can use the below
|
||||
* way to finish a complicated query:
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
|
||||
* .findFirst(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* Note that the associated models won't be loaded by default considering
|
||||
* the efficiency, but you can do that by using
|
||||
* {@link org.litepal.crud.ClusterQuery#findFirst(Class, boolean)}.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @return An object with founded data from database, or null.
|
||||
*/
|
||||
public <T> T findFirst(Class<T> modelClass) {
|
||||
return findFirst(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #findFirst(Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor findFirstAsync(Class<T> modelClass) {
|
||||
return findFirstAsync(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is mostly same as {@link org.litepal.crud.ClusterQuery#findFirst(Class)} but an isEager
|
||||
* parameter. If set true the associated models will be loaded as well.
|
||||
* <br>
|
||||
* Note that isEager will only work for one deep level relation, considering the query efficiency.
|
||||
* You have to implement on your own if you need to load multiple deepness of relation at once.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object with founded data from database, or null.
|
||||
*/
|
||||
public <T> T findFirst(Class<T> modelClass, boolean isEager) {
|
||||
List<T> list = find(modelClass, isEager);
|
||||
if (list.size() > 0) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #findFirst(Class, boolean)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor findFirstAsync(final Class<T> modelClass, final boolean isEager) {
|
||||
final FindExecutor executor = new FindExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final T t = findFirst(modelClass, isEager);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the last record by the cluster parameters. You can use the below
|
||||
* way to finish a complicated query:
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
|
||||
* .findLast(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* Note that the associated models won't be loaded by default considering
|
||||
* the efficiency, but you can do that by using
|
||||
* {@link org.litepal.crud.ClusterQuery#findLast(Class, boolean)}.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @return An object with founded data from database, or null.
|
||||
*/
|
||||
public <T> T findLast(Class<T> modelClass) {
|
||||
return findLast(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #findLast(Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor findLastAsync(Class<T> modelClass) {
|
||||
return findLastAsync(modelClass, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is mostly same as {@link org.litepal.crud.ClusterQuery#findLast(Class)} but an isEager
|
||||
* parameter. If set true the associated models will be loaded as well.
|
||||
* <br>
|
||||
* Note that isEager will only work for one deep level relation, considering the query efficiency.
|
||||
* You have to implement on your own if you need to load multiple deepness of relation at once.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object with founded data from database, or null.
|
||||
*/
|
||||
public <T> T findLast(Class<T> modelClass, boolean isEager) {
|
||||
List<T> list = find(modelClass, isEager);
|
||||
int size = list.size();
|
||||
if (size > 0) {
|
||||
return list.get(size - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #findLast(Class, boolean)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor findLastAsync(final Class<T> modelClass, final boolean isEager) {
|
||||
final FindExecutor executor = new FindExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final T t = findLast(modelClass, isEager);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the records.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.count(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* This will count all rows in person table.<br>
|
||||
* You can also specify a where clause when counting.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").count(Person.class);
|
||||
* </pre>
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @return Count of the specified table.
|
||||
*/
|
||||
public synchronized int count(Class<?> modelClass) {
|
||||
return count(BaseUtility.changeCase(modelClass.getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #count(Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @return A CountExecutor instance.
|
||||
*/
|
||||
public CountExecutor countAsync(Class<?> modelClass) {
|
||||
return countAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the records.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.count("person");
|
||||
* </pre>
|
||||
*
|
||||
* This will count all rows in person table.<br>
|
||||
* You can also specify a where clause when counting.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").count("person");
|
||||
* </pre>
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @return Count of the specified table.
|
||||
*/
|
||||
public synchronized int count(String tableName) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
return queryHandler.onCount(tableName, mConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #count(String)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @return A CountExecutor instance.
|
||||
*/
|
||||
public CountExecutor countAsync(final String tableName) {
|
||||
final CountExecutor executor = new CountExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final int count = count(tableName);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average value on a given column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.average(Person.class, "age");
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").average(Person.class, "age");
|
||||
* </pre>
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @return The average value on a given column.
|
||||
*/
|
||||
public synchronized double average(Class<?> modelClass, String column) {
|
||||
return average(BaseUtility.changeCase(modelClass.getSimpleName()), column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #average(Class, String)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @return A AverageExecutor instance.
|
||||
*/
|
||||
public AverageExecutor averageAsync(final Class<?> modelClass, final String column) {
|
||||
return averageAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average value on a given column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.average("person", "age");
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").average("person", "age");
|
||||
* </pre>
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @return The average value on a given column.
|
||||
*/
|
||||
public synchronized double average(String tableName, String column) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
return queryHandler.onAverage(tableName, column, mConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #average(String, String)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @return A AverageExecutor instance.
|
||||
*/
|
||||
public AverageExecutor averageAsync(final String tableName, final String column) {
|
||||
final AverageExecutor executor = new AverageExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final double average = average(tableName, column);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(average);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum value on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.max(Person.class, "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The maximum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T max(Class<?> modelClass, String columnName, Class<T> columnType) {
|
||||
return max(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #max(Class, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor maxAsync(final Class<?> modelClass, final String columnName, final Class<T> columnType) {
|
||||
return maxAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum value on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.max("person", "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").max("person", "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The maximum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T max(String tableName, String columnName, Class<T> columnType) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
return queryHandler.onMax(tableName, columnName, mConditions, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #max(String, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor maxAsync(final String tableName, final String columnName, final Class<T> columnType) {
|
||||
final FindExecutor executor = new FindExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final T t = max(tableName, columnName, columnType);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the minimum value on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.min(Person.class, "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The minimum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T min(Class<?> modelClass, String columnName, Class<T> columnType) {
|
||||
return min(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #min(Class, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor minAsync(final Class<?> modelClass, final String columnName, final Class<T> columnType) {
|
||||
return minAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the minimum value on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.min("person", "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").min("person", "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The minimum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T min(String tableName, String columnName, Class<T> columnType) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
return queryHandler.onMin(tableName, columnName, mConditions, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #min(String, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor minAsync(final String tableName, final String columnName, final Class<T> columnType) {
|
||||
final FindExecutor executor = new FindExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final T t = min(tableName, columnName, columnType);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the sum of values on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.sum(Person.class, "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The sum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T sum(Class<?> modelClass, String columnName, Class<T> columnType) {
|
||||
return sum(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #sum(Class, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query from by class.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor sumAsync(final Class<?> modelClass, final String columnName, final Class<T> columnType) {
|
||||
return sumAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the sum of values on a given column. The value is returned
|
||||
* with the same data type of the column.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.sum("person", "age", int.class);
|
||||
* </pre>
|
||||
*
|
||||
* You can also specify a where clause when calculating.
|
||||
*
|
||||
* <pre>
|
||||
* DataSupport.where("age > ?", "15").sum("person", "age", Integer.TYPE);
|
||||
* </pre>
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return The sum value on a given column.
|
||||
*/
|
||||
public synchronized <T> T sum(String tableName, String columnName, Class<T> columnType) {
|
||||
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
|
||||
return queryHandler.onSum(tableName, columnName, mConditions, columnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically same as {@link #sum(String, String, Class)} but pending to a new thread for executing.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param columnName
|
||||
* The based on column to calculate.
|
||||
* @param columnType
|
||||
* The type of the based on column.
|
||||
* @return A FindExecutor instance.
|
||||
*/
|
||||
public <T> FindExecutor sumAsync(final String tableName, final String columnName, final Class<T> columnType) {
|
||||
final FindExecutor executor = new FindExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DataSupport.class) {
|
||||
final T t = sum(tableName, columnName, columnType);
|
||||
if (executor.getListener() != null) {
|
||||
LitePal.getHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
executor.getListener().onFinish(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.submit(runnable);
|
||||
return executor;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.exceptions.DataSupportException;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.Const;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* This is a component under DataSupport. It deals with the deleting stuff as
|
||||
* primary task. If deletes a saved model or delete a record with id, the
|
||||
* cascade delete function would work. But considering efficiency, if deletes
|
||||
* with deleteAll method, the referenced data in other tables will not be
|
||||
* affected. Developers should remove those referenced data by their own.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class DeleteHandler extends DataHandler {
|
||||
|
||||
/**
|
||||
* To store associated tables of current model's table. Only used while
|
||||
* deleting by id and deleting all by model class.
|
||||
*/
|
||||
private List<String> foreignKeyTableToDelete;
|
||||
|
||||
/**
|
||||
* Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not
|
||||
* allow to create instance of DeleteHandler out of CRUD package.
|
||||
*
|
||||
* @param db
|
||||
* The instance of SQLiteDatabase.
|
||||
*/
|
||||
DeleteHandler(SQLiteDatabase db) {
|
||||
mDatabase = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to delete. Using
|
||||
* baseObj to decide which record to delete. The baseObj must be saved
|
||||
* already(using {@link org.litepal.crud.DataSupport#isSaved()} to test), or nothing will be
|
||||
* deleted. This method can action cascade delete. When the record is
|
||||
* deleted from database, all the referenced data such as foreign key value
|
||||
* will be removed too.
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete.
|
||||
* @return The number of rows affected. Including cascade delete rows.
|
||||
*/
|
||||
int onDelete(DataSupport baseObj) {
|
||||
if (baseObj.isSaved()) {
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(baseObj.getClassName());
|
||||
deleteGenericData(baseObj.getClass(), supportedGenericFields, baseObj.getBaseObjId());
|
||||
Collection<AssociationsInfo> associationInfos = analyzeAssociations(baseObj);
|
||||
int rowsAffected = deleteCascade(baseObj);
|
||||
rowsAffected += mDatabase.delete(baseObj.getTableName(), "id = "
|
||||
+ baseObj.getBaseObjId(), null);
|
||||
clearAssociatedModelSaveState(baseObj, associationInfos);
|
||||
return rowsAffected;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to delete. Using
|
||||
* modelClass to decide which table to delete from, and id to decide a
|
||||
* specific row. This method can action cascade delete. When the record is
|
||||
* deleted from database, all the referenced data such as foreign key value
|
||||
* will be removed too.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to delete from.
|
||||
* @param id
|
||||
* Which record to delete.
|
||||
* @return The number of rows affected. Including cascade delete rows.
|
||||
*/
|
||||
int onDelete(Class<?> modelClass, long id) {
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(modelClass.getName());
|
||||
deleteGenericData(modelClass, supportedGenericFields, id);
|
||||
analyzeAssociations(modelClass);
|
||||
int rowsAffected = deleteCascade(modelClass, id);
|
||||
rowsAffected += mDatabase.delete(getTableName(modelClass),
|
||||
"id = " + id, null);
|
||||
getForeignKeyTableToDelete().clear();
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to delete multiple
|
||||
* rows. Using tableName to decide which table to delete from, and
|
||||
* conditions representing the WHERE part of an SQL statement.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to delete from.
|
||||
* @param conditions
|
||||
* A string array representing the WHERE part of an SQL
|
||||
* statement.
|
||||
* @return The number of rows affected.
|
||||
*/
|
||||
int onDeleteAll(String tableName, String... conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mDatabase.delete(tableName, getWhereClause(conditions),
|
||||
getWhereArgs(conditions));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
int onDeleteAll(Class<?> modelClass, String... conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(modelClass.getName());
|
||||
if (!supportedGenericFields.isEmpty()) {
|
||||
List<DataSupport> list = (List<DataSupport>) DataSupport.select("id").where(conditions).find(modelClass);
|
||||
if (list.size() > 0) {
|
||||
long[] ids = new long[list.size()];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
DataSupport dataSupport = list.get(i);
|
||||
ids[i] = dataSupport.getBaseObjId();
|
||||
}
|
||||
deleteGenericData(modelClass, supportedGenericFields, ids);
|
||||
}
|
||||
}
|
||||
analyzeAssociations(modelClass);
|
||||
int rowsAffected = deleteAllCascade(modelClass, conditions);
|
||||
rowsAffected += mDatabase.delete(getTableName(modelClass), getWhereClause(conditions),
|
||||
getWhereArgs(conditions));
|
||||
getForeignKeyTableToDelete().clear();
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the associations of modelClass and store the associated tables.
|
||||
* The associated tables might be used when deleting referenced data of a
|
||||
* specified row.
|
||||
*
|
||||
* @param modelClass
|
||||
* To get associations of this class.
|
||||
*/
|
||||
private void analyzeAssociations(Class<?> modelClass) {
|
||||
Collection<AssociationsInfo> associationInfos = getAssociationInfo(modelClass
|
||||
.getName());
|
||||
for (AssociationsInfo associationInfo : associationInfos) {
|
||||
String associatedTableName = DBUtility
|
||||
.getTableNameByClassName(associationInfo
|
||||
.getAssociatedClassName());
|
||||
if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE
|
||||
|| associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
|
||||
String classHoldsForeignKey = associationInfo
|
||||
.getClassHoldsForeignKey();
|
||||
if (!modelClass.getName().equals(classHoldsForeignKey)) {
|
||||
getForeignKeyTableToDelete().add(associatedTableName);
|
||||
}
|
||||
} else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) {
|
||||
String joinTableName = DBUtility.getIntermediateTableName(
|
||||
getTableName(modelClass), associatedTableName);
|
||||
joinTableName = BaseUtility.changeCase(joinTableName);
|
||||
getForeignKeyTableToDelete().add(joinTableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the analyzed result of associations to delete referenced data. So
|
||||
* this method must be called after {@link #analyzeAssociations(Class)}.
|
||||
* There're two parts of referenced data to delete. The foreign key rows in
|
||||
* associated table and the foreign key rows in intermediate join table.
|
||||
*
|
||||
* @param modelClass
|
||||
* To get the table name and combine with id as a foreign key
|
||||
* column.
|
||||
* @param id
|
||||
* Delete all the rows which referenced with this id.
|
||||
* @return The number of rows affected in associated tables and intermediate
|
||||
* join tables.
|
||||
*/
|
||||
private int deleteCascade(Class<?> modelClass, long id) {
|
||||
int rowsAffected = 0;
|
||||
for (String associatedTableName : getForeignKeyTableToDelete()) {
|
||||
String fkName = getForeignKeyColumnName(getTableName(modelClass));
|
||||
rowsAffected += mDatabase.delete(associatedTableName, fkName
|
||||
+ " = " + id, null);
|
||||
}
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
private int deleteAllCascade(Class<?> modelClass, String... conditions) {
|
||||
int rowsAffected = 0;
|
||||
for (String associatedTableName : getForeignKeyTableToDelete()) {
|
||||
String tableName = getTableName(modelClass);
|
||||
String fkName = getForeignKeyColumnName(tableName);
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
whereClause.append(fkName).append(" in (select id from ");
|
||||
whereClause.append(tableName);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
whereClause.append(" where ").append(buildConditionString(conditions));
|
||||
}
|
||||
whereClause.append(")");
|
||||
rowsAffected += mDatabase.delete(associatedTableName,
|
||||
BaseUtility.changeCase(whereClause.toString()), null);
|
||||
}
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
private String buildConditionString(String... conditions) {
|
||||
int argCount = conditions.length - 1;
|
||||
String whereClause = conditions[0];
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
whereClause = whereClause.replaceFirst("\\?", "'" + conditions[i+1] + "'");
|
||||
}
|
||||
return whereClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the associations of baseObj and store the result in it. The
|
||||
* associations will be used when deleting referenced data of baseObj.
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete.
|
||||
*/
|
||||
private Collection<AssociationsInfo> analyzeAssociations(DataSupport baseObj) {
|
||||
try {
|
||||
Collection<AssociationsInfo> associationInfos = getAssociationInfo(baseObj
|
||||
.getClassName());
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
return associationInfos;
|
||||
} catch (Exception e) {
|
||||
throw new DataSupportException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear associated models' save state. After this method, the associated
|
||||
* models of baseObj which data is removed from database will become
|
||||
* unsaved.
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete.
|
||||
* @param associationInfos
|
||||
* The associated info.
|
||||
*/
|
||||
private void clearAssociatedModelSaveState(DataSupport baseObj,
|
||||
Collection<AssociationsInfo> associationInfos) {
|
||||
try {
|
||||
for (AssociationsInfo associationInfo : associationInfos) {
|
||||
if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE
|
||||
&& !baseObj.getClassName().equals(
|
||||
associationInfo.getClassHoldsForeignKey())) {
|
||||
Collection<DataSupport> associatedModels = getAssociatedModels(
|
||||
baseObj, associationInfo);
|
||||
if (associatedModels != null && !associatedModels.isEmpty()) {
|
||||
for (DataSupport model : associatedModels) {
|
||||
if (model != null) {
|
||||
model.clearSavedState();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
|
||||
DataSupport model = getAssociatedModel(baseObj,
|
||||
associationInfo);
|
||||
if (model != null) {
|
||||
model.clearSavedState();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DataSupportException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the analyzed result of associations to delete referenced data. So
|
||||
* this method must be called after
|
||||
* {@link #analyzeAssociations(org.litepal.crud.DataSupport)}. There're two parts of
|
||||
* referenced data to delete. The foreign key rows in associated tables and
|
||||
* the foreign key rows in intermediate join tables.
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete. Now contains associations info.
|
||||
* @return The number of rows affected in associated table and intermediate
|
||||
* join table.
|
||||
*/
|
||||
private int deleteCascade(DataSupport baseObj) {
|
||||
int rowsAffected;
|
||||
rowsAffected = deleteAssociatedForeignKeyRows(baseObj);
|
||||
rowsAffected += deleteAssociatedJoinTableRows(baseObj);
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the referenced rows of baseObj in associated tables(Many2One and
|
||||
* One2One conditions).
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete. Now contains associations info.
|
||||
* @return The number of rows affected in all associated tables.
|
||||
*/
|
||||
private int deleteAssociatedForeignKeyRows(DataSupport baseObj) {
|
||||
int rowsAffected = 0;
|
||||
Map<String, Set<Long>> associatedModelMap = baseObj
|
||||
.getAssociatedModelsMapWithFK();
|
||||
for (String associatedTableName : associatedModelMap.keySet()) {
|
||||
String fkName = getForeignKeyColumnName(baseObj.getTableName());
|
||||
rowsAffected += mDatabase.delete(associatedTableName, fkName
|
||||
+ " = " + baseObj.getBaseObjId(), null);
|
||||
}
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the referenced rows of baseObj in intermediate join
|
||||
* tables(Many2Many condition).
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to delete. Now contains associations info.
|
||||
* @return The number of rows affected in all intermediate join tables.
|
||||
*/
|
||||
private int deleteAssociatedJoinTableRows(DataSupport baseObj) {
|
||||
int rowsAffected = 0;
|
||||
Set<String> associatedTableNames = baseObj
|
||||
.getAssociatedModelsMapForJoinTable().keySet();
|
||||
for (String associatedTableName : associatedTableNames) {
|
||||
String joinTableName = DBUtility.getIntermediateTableName(
|
||||
baseObj.getTableName(), associatedTableName);
|
||||
String fkName = getForeignKeyColumnName(baseObj.getTableName());
|
||||
rowsAffected += mDatabase.delete(joinTableName, fkName + " = "
|
||||
+ baseObj.getBaseObjId(), null);
|
||||
}
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the associated tables of current model's table. Only used while
|
||||
* deleting by id.
|
||||
*
|
||||
* @return All the associated tables of current model's table.
|
||||
*/
|
||||
private List<String> getForeignKeyTableToDelete() {
|
||||
if (foreignKeyTableToDelete == null) {
|
||||
foreignKeyTableToDelete = new ArrayList<String>();
|
||||
}
|
||||
return foreignKeyTableToDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the generic data in generic tables while main data was deleted.
|
||||
* @param modelClass
|
||||
* Used to get the generic table name and value id column.
|
||||
* @param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @param ids
|
||||
* The id array of models.
|
||||
*/
|
||||
private void deleteGenericData(Class<?> modelClass, List<Field> supportedGenericFields, long... ids) {
|
||||
for (Field field : supportedGenericFields) {
|
||||
String tableName = DBUtility.getGenericTableName(modelClass.getName(), field.getName());
|
||||
String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(modelClass.getName());
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
boolean needOr = false;
|
||||
for (long id : ids) {
|
||||
if (needOr) {
|
||||
whereClause.append(" or ");
|
||||
}
|
||||
whereClause.append(genericValueIdColumnName).append(" = ").append(id);
|
||||
needOr = true;
|
||||
}
|
||||
mDatabase.delete(tableName, whereClause.toString(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.litepal.exceptions.DataSupportException;
|
||||
|
||||
/**
|
||||
* This provides a send method to allow calling method in dynamic way. (Just
|
||||
* like Ruby, but not clean or easy as Ruby to use).
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class DynamicExecutor {
|
||||
|
||||
/**
|
||||
* Disable to create an instance of DynamicExecutor.
|
||||
*/
|
||||
private DynamicExecutor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method use java reflect API to execute method dynamically. Most
|
||||
* importantly, it could access the methods with private modifier to break
|
||||
* encapsulation.
|
||||
*
|
||||
* @param object
|
||||
* The object to invoke method.
|
||||
* @param methodName
|
||||
* The method name to invoke.
|
||||
* @param parameters
|
||||
* The parameters.
|
||||
* @param objectClass
|
||||
* Use objectClass to find method to invoke.
|
||||
* @param parameterTypes
|
||||
* The parameter types.
|
||||
* @return Returns the result of dynamically invoking method.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
static Object send(Object object, String methodName, Object[] parameters, Class<?> objectClass,
|
||||
Class<?>[] parameterTypes) throws SecurityException, IllegalArgumentException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
if (parameters == null) {
|
||||
parameters = new Object[] {};
|
||||
}
|
||||
if (parameterTypes == null) {
|
||||
parameterTypes = new Class[] {};
|
||||
}
|
||||
Method method = objectClass.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method.invoke(object, parameters);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new DataSupportException(DataSupportException.noSuchMethodException(
|
||||
objectClass.getSimpleName(), methodName), e);
|
||||
}
|
||||
}
|
||||
|
||||
static void set(Object object, String fieldName, Object value, Class<?> objectClass)
|
||||
throws SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException {
|
||||
Field objectField = objectClass.getDeclaredField(fieldName);
|
||||
objectField.setAccessible(true);
|
||||
objectField.set(object, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method use java reflect API to set field value dynamically. Most
|
||||
* importantly, it could access fields with private modifier to break
|
||||
* encapsulation.
|
||||
*
|
||||
* @param object
|
||||
* The object to access.
|
||||
* @param fieldName
|
||||
* The field name to access.
|
||||
* @param value
|
||||
* Assign this value to field.
|
||||
* @param objectClass
|
||||
* The class of object.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
static void setField(Object object, String fieldName, Object value, Class<?> objectClass)
|
||||
throws SecurityException, IllegalArgumentException, IllegalAccessException {
|
||||
if (objectClass == DataSupport.class || objectClass == Object.class) {
|
||||
throw new DataSupportException(DataSupportException.noSuchFieldExceptioin(
|
||||
objectClass.getSimpleName(), fieldName));
|
||||
}
|
||||
try {
|
||||
set(object, fieldName, value, objectClass);
|
||||
} catch (NoSuchFieldException e) {
|
||||
setField(object, fieldName, value, objectClass.getSuperclass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method use java reflect API to get field value dynamically. Most
|
||||
* importantly, it could access fields with private modifier to break
|
||||
* encapsulation.
|
||||
*
|
||||
* @param object
|
||||
* The object to access.
|
||||
* @param fieldName
|
||||
* The field name to access.
|
||||
* @param objectClass
|
||||
* The class of object.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
static Object getField(Object object, String fieldName, Class<?> objectClass)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
if (objectClass == DataSupport.class || objectClass == Object.class) {
|
||||
throw new DataSupportException(DataSupportException.noSuchFieldExceptioin(
|
||||
objectClass.getSimpleName(), fieldName));
|
||||
}
|
||||
try {
|
||||
Field objectField = objectClass.getDeclaredField(fieldName);
|
||||
objectField.setAccessible(true);
|
||||
return objectField.get(object);
|
||||
} catch (NoSuchFieldException e) {
|
||||
return getField(object, fieldName, objectClass.getSuperclass());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.litepal.LitePalBase;
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.tablemanager.Connector;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* Deals analysis work when comes to two models are associated with Many2Many
|
||||
* associations.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
public class Many2ManyAnalyzer extends AssociationsAnalyzer {
|
||||
|
||||
/**
|
||||
* Analyzing the AssociationInfo. It will help baseObj assign the necessary
|
||||
* values automatically. If the two associated models have bidirectional
|
||||
* associations in class files but developer has only build unidirectional
|
||||
* associations in models, it will force to build the bidirectional
|
||||
* associations. Besides the
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelForJoinTable(String, long)} will be called
|
||||
* here to put right values into tables.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException,
|
||||
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
Collection<DataSupport> associatedModels = getAssociatedModels(baseObj, associationInfo);
|
||||
declareAssociations(baseObj, associationInfo);
|
||||
if (associatedModels != null) {
|
||||
for (DataSupport associatedModel : associatedModels) {
|
||||
Collection<DataSupport> tempCollection = getReverseAssociatedModels(
|
||||
associatedModel, associationInfo);
|
||||
Collection<DataSupport> reverseAssociatedModels = checkAssociatedModelCollection(
|
||||
tempCollection, associationInfo.getAssociateSelfFromOtherModel());
|
||||
addNewModelForAssociatedModel(reverseAssociatedModels, baseObj);
|
||||
setReverseAssociatedModels(associatedModel, associationInfo,
|
||||
reverseAssociatedModels);
|
||||
dealAssociatedModel(baseObj, associatedModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This add an empty set for {@link org.litepal.crud.DataSupport#associatedModelsMapForJoinTable}.
|
||||
* Might use for updating intermediate join table.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associationInfo
|
||||
* To get associated table name from.
|
||||
*/
|
||||
private void declareAssociations(DataSupport baseObj, AssociationsInfo associationInfo) {
|
||||
baseObj.addEmptyModelForJoinTable(getAssociatedTableName(associationInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Force to build bidirectional associations for the associated model. If it
|
||||
* has already built, ignoring the rest process.
|
||||
*
|
||||
* @param associatedModelCollection
|
||||
* The associated models collection of the associated model. Add
|
||||
* self model into it if it doesn't contain self model yet.
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
*/
|
||||
private void addNewModelForAssociatedModel(Collection<DataSupport> associatedModelCollection,
|
||||
DataSupport baseObj) {
|
||||
if (!associatedModelCollection.contains(baseObj)) {
|
||||
associatedModelCollection.add(baseObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* First of all the associated model need to be saved already, or nothing
|
||||
* will be executed below. Then add the id of associated model into
|
||||
* {@link org.litepal.crud.DataSupport#associatedModelsMapForJoinTable} for
|
||||
* inserting value into intermediate join table after baseObj is saved.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
*/
|
||||
private void dealAssociatedModel(DataSupport baseObj, DataSupport associatedModel) {
|
||||
if (associatedModel.isSaved()) {
|
||||
baseObj.addAssociatedModelForJoinTable(associatedModel.getTableName(),
|
||||
associatedModel.getBaseObjId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated table name by {@link org.litepal.crud.model.AssociationsInfo} after case
|
||||
* changed.
|
||||
*
|
||||
* @param associationInfo
|
||||
* To get the associated table name from.
|
||||
* @return The name of associated table with changed case.
|
||||
*/
|
||||
private String getAssociatedTableName(AssociationsInfo associationInfo) {
|
||||
return BaseUtility.changeCase(DBUtility.getTableNameByClassName(associationInfo
|
||||
.getAssociatedClassName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the associations between self model and associated model is
|
||||
* already saved into intermediate join table.<br>
|
||||
* Make sure baseObj and associatedModel are saved already, or the result
|
||||
* might be wrong.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @return If the associations between them is saved into intermediate join
|
||||
* table, return true. Otherwise return false.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Deprecated
|
||||
private boolean isDataExists(DataSupport baseObj, DataSupport associatedModel) {
|
||||
boolean exists = false;
|
||||
SQLiteDatabase db = Connector.getDatabase();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = db.query(getJoinTableName(baseObj, associatedModel), null,
|
||||
getSelection(baseObj, associatedModel),
|
||||
getSelectionArgs(baseObj, associatedModel), null, null, null);
|
||||
exists = cursor.getCount() > 0;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the selection for querying the data in table. Column names are the
|
||||
* table names with _id as suffix.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @return The selection clause for querying data.
|
||||
*/
|
||||
private String getSelection(DataSupport baseObj, DataSupport associatedModel) {
|
||||
StringBuilder where = new StringBuilder();
|
||||
where.append(getForeignKeyColumnName(baseObj.getTableName()));
|
||||
where.append(" = ? and ");
|
||||
where.append(getForeignKeyColumnName(associatedModel.getTableName()));
|
||||
where.append(" = ?");
|
||||
return where.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the selection arguments to fill selection clause.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @return The selection arguments with the id of baseObj and
|
||||
* associatedModel to fill.
|
||||
*/
|
||||
private String[] getSelectionArgs(DataSupport baseObj, DataSupport associatedModel) {
|
||||
return new String[] { String.valueOf(baseObj.getBaseObjId()),
|
||||
String.valueOf(associatedModel.getBaseObjId()) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the intermediate join table name for self model and associated model.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @return The intermediate join table name.
|
||||
*/
|
||||
private String getJoinTableName(DataSupport baseObj, DataSupport associatedModel) {
|
||||
return getIntermediateTableName(baseObj, associatedModel.getTableName());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.litepal.LitePalBase;
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
/**
|
||||
* Deals analysis work when comes to two models are associated with Many2One
|
||||
* associations.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class Many2OneAnalyzer extends AssociationsAnalyzer {
|
||||
|
||||
/**
|
||||
* Analyzing the AssociationInfo. It will help baseObj assign the necessary
|
||||
* values automatically. If the two associated models have bidirectional
|
||||
* associations in class files but developer has only build unidirectional
|
||||
* associations in models, it will force to build the bidirectional
|
||||
* associations. Besides
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)} and
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)} will be
|
||||
* called here to put right values into tables.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException,
|
||||
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (baseObj.getClassName().equals(associationInfo.getClassHoldsForeignKey())) {
|
||||
analyzeManySide(baseObj, associationInfo);
|
||||
} else {
|
||||
analyzeOneSide(baseObj, associationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When it's on the M side. Get the associated model first, then use it to
|
||||
* get the associated model collection on the O side. Initialize the
|
||||
* collection by calling
|
||||
* {@link #checkAssociatedModelCollection(java.util.Collection, java.lang.reflect.Field)}
|
||||
* and calling
|
||||
* {@link #dealAssociatedModelOnManySide(java.util.Collection, org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)}
|
||||
* to set foreign key.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
private void analyzeManySide(DataSupport baseObj, AssociationsInfo associationInfo)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
DataSupport associatedModel = getAssociatedModel(baseObj, associationInfo);
|
||||
if (associatedModel != null) {
|
||||
// now it's m2o bidirectional association.
|
||||
Collection<DataSupport> tempCollection = getReverseAssociatedModels(associatedModel,
|
||||
associationInfo);
|
||||
Collection<DataSupport> reverseAssociatedModels = checkAssociatedModelCollection(
|
||||
tempCollection, associationInfo.getAssociateSelfFromOtherModel());
|
||||
setReverseAssociatedModels(associatedModel, associationInfo, reverseAssociatedModels);
|
||||
dealAssociatedModelOnManySide(reverseAssociatedModels, baseObj, associatedModel);
|
||||
} else {
|
||||
mightClearFKValue(baseObj, associationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When it's on the O side. Get the associated model collection first, then
|
||||
* iterate all the associated models. Each associated model calls
|
||||
* {@link #buildBidirectionalAssociations(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport, org.litepal.crud.model.AssociationsInfo)}
|
||||
* to build bidirectional association if they haven't built yet. Then calls
|
||||
* {@link #dealAssociatedModelOnOneSide(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)} to set
|
||||
* foreign key.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
private void analyzeOneSide(DataSupport baseObj, AssociationsInfo associationInfo)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
Collection<DataSupport> associatedModels = getAssociatedModels(baseObj, associationInfo);
|
||||
if (associatedModels == null || associatedModels.isEmpty()) {
|
||||
String tableName = DBUtility.getTableNameByClassName(associationInfo
|
||||
.getAssociatedClassName());
|
||||
baseObj.addAssociatedTableNameToClearFK(tableName);
|
||||
return;
|
||||
}
|
||||
for (DataSupport associatedModel : associatedModels) {
|
||||
buildBidirectionalAssociations(baseObj, associatedModel, associationInfo);
|
||||
dealAssociatedModelOnOneSide(baseObj, associatedModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the baseObj is already existed in the associatedModels
|
||||
* collection. If not add baseObj into the collection. Then if the
|
||||
* associated model is saved, add its' name and id to baseObj by calling
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}.
|
||||
*
|
||||
* @param associatedModels
|
||||
* The associated model collection.
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
*/
|
||||
private void dealAssociatedModelOnManySide(Collection<DataSupport> associatedModels,
|
||||
DataSupport baseObj, DataSupport associatedModel) {
|
||||
if (!associatedModels.contains(baseObj)) {
|
||||
associatedModels.add(baseObj);
|
||||
}
|
||||
if (associatedModel.isSaved()) {
|
||||
baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(),
|
||||
associatedModel.getBaseObjId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with associated model on one side.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
*/
|
||||
private void dealAssociatedModelOnOneSide(DataSupport baseObj, DataSupport associatedModel) {
|
||||
dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.litepal.LitePalBase;
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
/**
|
||||
* Deals analysis work when comes to two models are associated with One2One
|
||||
* associations.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
public class One2OneAnalyzer extends AssociationsAnalyzer {
|
||||
|
||||
/**
|
||||
* Analyzing the AssociationInfo. It will help baseObj assign the necessary
|
||||
* values automatically. If the two associated models have bidirectional
|
||||
* associations in class files but developer has only build unidirectional
|
||||
* associations in models, it will force to build the bidirectional
|
||||
* associations. Besides
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)} and
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)} will be
|
||||
* called here to put right values into tables.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist or update.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
* @throws SecurityException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalAccessException
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
*/
|
||||
void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException,
|
||||
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
DataSupport associatedModel = getAssociatedModel(baseObj, associationInfo);
|
||||
if (associatedModel != null) {
|
||||
buildBidirectionalAssociations(baseObj, associatedModel, associationInfo);
|
||||
dealAssociatedModel(baseObj, associatedModel, associationInfo);
|
||||
} else {
|
||||
String tableName = DBUtility.getTableNameByClassName(associationInfo
|
||||
.getAssociatedClassName());
|
||||
baseObj.addAssociatedTableNameToClearFK(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the association type. If it's bidirectional association, calls
|
||||
* {@link #bidirectionalCondition(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)}. If it's
|
||||
* unidirectional association, calls
|
||||
* {@link #unidirectionalCondition(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)}.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
* @param associationInfo
|
||||
* The associated info analyzed by
|
||||
* {@link LitePalBase#getAssociationInfo(String)}.
|
||||
*/
|
||||
private void dealAssociatedModel(DataSupport baseObj, DataSupport associatedModel,
|
||||
AssociationsInfo associationInfo) {
|
||||
if (associationInfo.getAssociateSelfFromOtherModel() != null) {
|
||||
bidirectionalCondition(baseObj, associatedModel);
|
||||
} else {
|
||||
unidirectionalCondition(baseObj, associatedModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals bidirectional association condition. If associated model is saved,
|
||||
* add its' name and id to baseObj by calling
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}. Add its' name
|
||||
* and id to baseObj by calling
|
||||
* {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)}.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
*/
|
||||
private void bidirectionalCondition(DataSupport baseObj, DataSupport associatedModel) {
|
||||
if (associatedModel.isSaved()) {
|
||||
// use to update associated table after saving
|
||||
baseObj.addAssociatedModelWithFK(associatedModel.getTableName(),
|
||||
associatedModel.getBaseObjId());
|
||||
// use to add foreign key value while saving
|
||||
baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(),
|
||||
associatedModel.getBaseObjId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals unidirectional associations condition.
|
||||
*
|
||||
* @param baseObj
|
||||
* The baseObj currently want to persist.
|
||||
* @param associatedModel
|
||||
* The associated model of baseObj.
|
||||
*/
|
||||
private void unidirectionalCondition(DataSupport baseObj, DataSupport associatedModel) {
|
||||
dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* This is a component under DataSupport. It deals with query stuff as primary
|
||||
* task.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class QueryHandler extends DataHandler {
|
||||
|
||||
/**
|
||||
* Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not
|
||||
* allow to create instance of QueryHandler out of CRUD package.
|
||||
*
|
||||
* @param db
|
||||
* The instance of SQLiteDatabase.
|
||||
*/
|
||||
QueryHandler(SQLiteDatabase db) {
|
||||
mDatabase = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to query a record
|
||||
* based on id. If the result set is empty, gives null back.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param id
|
||||
* Which record to query.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object with found data from database, or null.
|
||||
*/
|
||||
<T> T onFind(Class<T> modelClass, long id, boolean isEager) {
|
||||
List<T> dataList = query(modelClass, null, "id = ?", new String[] { String.valueOf(id) },
|
||||
null, null, null, null, getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
if (dataList.size() > 0) {
|
||||
return dataList.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to query the first
|
||||
* record in a table. If the result set is empty, gives null back.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object with data of first row, or null.
|
||||
*/
|
||||
<T> T onFindFirst(Class<T> modelClass, boolean isEager) {
|
||||
List<T> dataList = query(modelClass, null, null, null, null, null, "id", "1",
|
||||
getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
if (dataList.size() > 0) {
|
||||
return dataList.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to query the last
|
||||
* record in a table. If the result set is empty, gives null back.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object with data of last row, or null.
|
||||
*/
|
||||
<T> T onFindLast(Class<T> modelClass, boolean isEager) {
|
||||
List<T> dataList = query(modelClass, null, null, null, null, null, "id desc", "1",
|
||||
getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
if (dataList.size() > 0) {
|
||||
return dataList.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to query multiple
|
||||
* records by an id array. Pass no ids means query all rows.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @param ids
|
||||
* Which records to query. Or do not pass it to find all records.
|
||||
* @return An object list with found data from database, or an empty list.
|
||||
*/
|
||||
<T> List<T> onFindAll(Class<T> modelClass, boolean isEager, long... ids) {
|
||||
List<T> dataList;
|
||||
if (isAffectAllLines(ids)) {
|
||||
dataList = query(modelClass, null, null, null, null, null, "id", null,
|
||||
getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
} else {
|
||||
dataList = query(modelClass, null, getWhereOfIdsWithOr(ids), null, null, null, "id",
|
||||
null, getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to query multiple
|
||||
* records by parameters.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to query and the object type to return as a list.
|
||||
* @param columns
|
||||
* A String array of which columns to return. Passing null will
|
||||
* return all columns.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @param orderBy
|
||||
* How to order the rows, formatted as an SQL ORDER BY clause.
|
||||
* Passing null will use the default sort order, which may be
|
||||
* unordered.
|
||||
* @param limit
|
||||
* Limits the number of rows returned by the query, formatted as
|
||||
* LIMIT clause.
|
||||
* @param isEager
|
||||
* True to load the associated models, false not.
|
||||
* @return An object list with found data from database, or an empty list.
|
||||
*/
|
||||
<T> List<T> onFind(Class<T> modelClass, String[] columns, String[] conditions, String orderBy,
|
||||
String limit, boolean isEager) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
orderBy = DBUtility.convertOrderByClauseToValidName(orderBy);
|
||||
return query(modelClass, columns, getWhereClause(conditions),
|
||||
getWhereArgs(conditions), null, null, orderBy, limit,
|
||||
getForeignKeyAssociations(modelClass.getName(), isEager));
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to Count the
|
||||
* records.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @return Count of the specified table.
|
||||
*/
|
||||
int onCount(String tableName, String[] conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mathQuery(tableName, new String[] { "count(1)" }, conditions, int.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to calculate the
|
||||
* average value on a given column.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @return The average value on a given column.
|
||||
*/
|
||||
double onAverage(String tableName, String column, String[] conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mathQuery(tableName, new String[] { "avg(" + column + ")" }, conditions,
|
||||
double.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to calculate the
|
||||
* maximum value on a given column.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @param type
|
||||
* The type of the based on column.
|
||||
* @return The maximum value on a given column.
|
||||
*/
|
||||
<T> T onMax(String tableName, String column, String[] conditions, Class<T> type) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mathQuery(tableName, new String[] { "max(" + column + ")" }, conditions, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to calculate the
|
||||
* minimum value on a given column.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @param type
|
||||
* The type of the based on column.
|
||||
* @return The minimum value on a given column.
|
||||
*/
|
||||
<T> T onMin(String tableName, String column, String[] conditions, Class<T> type) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mathQuery(tableName, new String[] { "min(" + column + ")" }, conditions, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to calculate the sum
|
||||
* of values on a given column.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to query from.
|
||||
* @param column
|
||||
* The based on column to calculate.
|
||||
* @param conditions
|
||||
* A filter declaring which rows to return, formatted as an SQL
|
||||
* WHERE clause. Passing null will return all rows.
|
||||
* @param type
|
||||
* The type of the based on column.
|
||||
* @return The sum value on a given column.
|
||||
*/
|
||||
<T> T onSum(String tableName, String column, String[] conditions, Class<T> type) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
return mathQuery(tableName, new String[] { "sum(" + column + ")" }, conditions, type);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,582 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.exceptions.DataSupportException;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.litepal.util.BaseUtility.changeCase;
|
||||
|
||||
/**
|
||||
* This is a component under DataSupport. It deals with the saving stuff as
|
||||
* primary task. All the implementation based on the java reflection API and
|
||||
* Android SQLiteDatabase API. It will persist the model class into table. If
|
||||
* there're some associated models already persisted, it will build the
|
||||
* associations in database automatically between the current model and the
|
||||
* associated models.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class SaveHandler extends DataHandler {
|
||||
|
||||
/**
|
||||
* indicates that associations can be ignored while saving.
|
||||
*/
|
||||
private boolean ignoreAssociations = false;
|
||||
|
||||
private ContentValues values;
|
||||
|
||||
/**
|
||||
* Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not
|
||||
* allow to create instance of SaveHandler out of CRUD package.
|
||||
*
|
||||
* @param db
|
||||
* The instance of SQLiteDatabase.
|
||||
*/
|
||||
SaveHandler(SQLiteDatabase db) {
|
||||
values = new ContentValues();
|
||||
mDatabase = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to save a model. It
|
||||
* is called when a model class calls the save method. First of all, the
|
||||
* passed in baseObj will be saved into database. Then LitePal will analyze
|
||||
* the associations. If there're associated models detected, each associated
|
||||
* model which is persisted will build association with current model in
|
||||
* database.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to persist.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
|
||||
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
String className = baseObj.getClassName();
|
||||
List<Field> supportedFields = getSupportedFields(className);
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(className);
|
||||
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
|
||||
if (!baseObj.isSaved()) {
|
||||
if (!ignoreAssociations) {
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
}
|
||||
doSaveAction(baseObj, supportedFields, supportedGenericFields);
|
||||
if (!ignoreAssociations) {
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
}
|
||||
} else {
|
||||
if (!ignoreAssociations) {
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
}
|
||||
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to save a model. It
|
||||
* is called when a model class calls the save method. This method will only
|
||||
* save the baseObj into database without analyzing any association, so that
|
||||
* the saving process will be faster.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to persist.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
void onSaveFast(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
|
||||
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
ignoreAssociations = true;
|
||||
onSave(baseObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to save a model
|
||||
* collection. It is called when developer calls
|
||||
* {@link org.litepal.crud.DataSupport#saveAll(java.util.Collection)}. Each model in the collection
|
||||
* will be persisted. If there're associated models detected, each
|
||||
* associated model which is persisted will build association with current
|
||||
* model in database.
|
||||
*
|
||||
* @param collection
|
||||
* Holds all models to persist.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
<T extends DataSupport> void onSaveAll(Collection<T> collection) throws SecurityException,
|
||||
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (collection != null && collection.size() > 0) {
|
||||
DataSupport[] array = collection.toArray(new DataSupport[0]);
|
||||
DataSupport firstObj = array[0];
|
||||
String className = firstObj.getClassName();
|
||||
List<Field> supportedFields = getSupportedFields(className);
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(className);
|
||||
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
|
||||
for (DataSupport baseObj : array) {
|
||||
if (!baseObj.isSaved()) {
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
doSaveAction(baseObj, supportedFields, supportedGenericFields);
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
} else {
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
|
||||
}
|
||||
baseObj.clearAssociatedData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persisting model class into database happens here. But first
|
||||
* {@link #beforeSave(org.litepal.crud.DataSupport, java.util.List, android.content.ContentValues)} will be called to
|
||||
* put the values for ContentValues. When the model is saved,
|
||||
* {@link #afterSave(org.litepal.crud.DataSupport, java.util.List, java.util.List, long)} will be called to do stuffs
|
||||
* after model is saved. Note that SaveSupport won't help with id. Any
|
||||
* developer who wants to set value to id will be ignored here. The value of
|
||||
* id will be generated by SQLite automatically.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to persist.
|
||||
* @param supportedFields
|
||||
* List of all supported fields.
|
||||
* @param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
private void doSaveAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
values.clear();
|
||||
beforeSave(baseObj, supportedFields, values);
|
||||
long id = saving(baseObj, values);
|
||||
afterSave(baseObj, supportedFields, supportedGenericFields, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before the self model is saved, it will be analyzed first. Put all the
|
||||
* data contained by the model into ContentValues, including the fields
|
||||
* value and foreign key value.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to persist.
|
||||
* @param supportedFields
|
||||
* List of all supported fields.
|
||||
* @param values
|
||||
* To store data of current model for persisting.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
private void beforeSave(DataSupport baseObj, List<Field> supportedFields, ContentValues values)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
putFieldsValue(baseObj, supportedFields, values);
|
||||
if (!ignoreAssociations) {
|
||||
putForeignKeyValue(values, baseObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling {@link android.database.sqlite.SQLiteDatabase#insert(String, String, android.content.ContentValues)} to
|
||||
* persist the current model.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to persist.
|
||||
* @param values
|
||||
* To store data of current model for persisting.
|
||||
* @return The row ID of the newly inserted row, or -1 if an error occurred.
|
||||
*/
|
||||
private long saving(DataSupport baseObj, ContentValues values) {
|
||||
if (values.size() == 0) {
|
||||
values.putNull("id");
|
||||
}
|
||||
return mDatabase.insert(baseObj.getTableName(), null, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* After the model is saved, do the extra work that need to do.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
* @param supportedFields
|
||||
* List of all supported fields.
|
||||
* @param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @param id
|
||||
* The current model's id.
|
||||
*/
|
||||
private void afterSave(DataSupport baseObj, List<Field> supportedFields,
|
||||
List<Field> supportedGenericFields, long id) throws IllegalAccessException, InvocationTargetException {
|
||||
throwIfSaveFailed(id);
|
||||
assignIdValue(baseObj, getIdField(supportedFields), id);
|
||||
updateGenericTables(baseObj, supportedGenericFields, id);
|
||||
if (!ignoreAssociations) {
|
||||
updateAssociatedTableWithFK(baseObj);
|
||||
insertIntermediateJoinTableValue(baseObj, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a model is associated with two different models.
|
||||
*
|
||||
* @param baseObj
|
||||
* The class of base object.
|
||||
* @param supportedFields
|
||||
* List of all supported fields.
|
||||
* @param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
private void doUpdateAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
values.clear();
|
||||
beforeUpdate(baseObj, supportedFields, values);
|
||||
updating(baseObj, values);
|
||||
afterUpdate(baseObj, supportedGenericFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before updating model, it will be analyzed first. Put all the data
|
||||
* contained by the model into ContentValues, including the fields value and
|
||||
* foreign key value. If the associations between models has been removed.
|
||||
* The foreign key value in database should be cleared too.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to update.
|
||||
* @param supportedFields
|
||||
* List of all supported fields.
|
||||
* @param values
|
||||
* To store data of current model for updating.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
private void beforeUpdate(DataSupport baseObj, List<Field> supportedFields, ContentValues values)
|
||||
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
putFieldsValue(baseObj, supportedFields, values);
|
||||
if (!ignoreAssociations) {
|
||||
putForeignKeyValue(values, baseObj);
|
||||
for (String fkName : baseObj.getListToClearSelfFK()) {
|
||||
values.putNull(fkName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling
|
||||
* {@link android.database.sqlite.SQLiteDatabase#update(String, android.content.ContentValues, String, String[])} to
|
||||
* update the current model.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model to update.
|
||||
* @param values
|
||||
* To store data of current model for updating.
|
||||
*/
|
||||
private void updating(DataSupport baseObj, ContentValues values) {
|
||||
mDatabase.update(baseObj.getTableName(), values, "id = ?",
|
||||
new String[] { String.valueOf(baseObj.getBaseObjId()) });
|
||||
}
|
||||
|
||||
/**
|
||||
* After the model is updated, do the extra work that need to do.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is updated.
|
||||
* @param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
*/
|
||||
private void afterUpdate(DataSupport baseObj, List<Field> supportedGenericFields)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
updateGenericTables(baseObj, supportedGenericFields, baseObj.getBaseObjId());
|
||||
if (!ignoreAssociations) {
|
||||
updateAssociatedTableWithFK(baseObj);
|
||||
insertIntermediateJoinTableValue(baseObj, true);
|
||||
clearFKValueInAssociatedTable(baseObj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id field by the passed in field list.
|
||||
*
|
||||
* @param supportedFields
|
||||
* The field list to find from.
|
||||
* @return The id field. If not found one return null.
|
||||
*/
|
||||
private Field getIdField(List<Field> supportedFields) {
|
||||
for (Field field : supportedFields) {
|
||||
if (isIdColumn(field.getName())) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the model saved failed, throw an exception.
|
||||
*
|
||||
* @param id
|
||||
* The id returned by SQLite. -1 means saved failed.
|
||||
*/
|
||||
private void throwIfSaveFailed(long id) {
|
||||
if (id == -1) {
|
||||
throw new DataSupportException(DataSupportException.SAVE_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the generated id value to the model. The
|
||||
* {@link org.litepal.crud.DataSupport#baseObjId} will be assigned anyway. If the model has a
|
||||
* field named id or _id, LitePal will assign it too. The
|
||||
* {@link org.litepal.crud.DataSupport#baseObjId} will be used as identify of this model for
|
||||
* system use. The id or _id field will help developers for their own
|
||||
* purpose.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
* @param idField
|
||||
* The field of id.
|
||||
* @param id
|
||||
* The value of id.
|
||||
*/
|
||||
private void assignIdValue(DataSupport baseObj, Field idField, long id) {
|
||||
try {
|
||||
giveBaseObjIdValue(baseObj, id);
|
||||
if (idField != null) {
|
||||
giveModelIdValue(baseObj, idField.getName(), idField.getType(), id);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DataSupportException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After saving a model, the id for this model will be returned. Assign this
|
||||
* id to the model's id or _id field if it exists.
|
||||
*
|
||||
* @param baseObj
|
||||
* The class of base object.
|
||||
* @param idName
|
||||
* The name of id. Only id or _id is valid.
|
||||
* @param idType
|
||||
* The type of id. Only int or long is valid.
|
||||
* @param id
|
||||
* The value of id.
|
||||
* @throws SecurityException
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
private void giveModelIdValue(DataSupport baseObj, String idName, Class<?> idType, long id)
|
||||
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
if (shouldGiveModelIdValue(idName, idType, id)) {
|
||||
Object value;
|
||||
if (idType == int.class || idType == Integer.class) {
|
||||
value = (int) id;
|
||||
} else if (idType == long.class || idType == Long.class) {
|
||||
value = id;
|
||||
} else {
|
||||
throw new DataSupportException(DataSupportException.ID_TYPE_INVALID_EXCEPTION);
|
||||
}
|
||||
DynamicExecutor.setField(baseObj, idName, value, baseObj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the table for this model have a foreign key column, the value of
|
||||
* foreign key id should be saved too.
|
||||
*
|
||||
* @param values
|
||||
* The instance of ContentValues to put foreign key value.
|
||||
*/
|
||||
private void putForeignKeyValue(ContentValues values, DataSupport baseObj) {
|
||||
Map<String, Long> associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK();
|
||||
for (String associatedTableName : associatedModelMap.keySet()) {
|
||||
values.put(getForeignKeyColumnName(associatedTableName),
|
||||
associatedModelMap.get(associatedTableName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the foreign keys in the associated model's table.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
*/
|
||||
private void updateAssociatedTableWithFK(DataSupport baseObj) {
|
||||
Map<String, Set<Long>> associatedModelMap = baseObj.getAssociatedModelsMapWithFK();
|
||||
ContentValues values = new ContentValues();
|
||||
for (String associatedTableName : associatedModelMap.keySet()) {
|
||||
values.clear();
|
||||
String fkName = getForeignKeyColumnName(baseObj.getTableName());
|
||||
values.put(fkName, baseObj.getBaseObjId());
|
||||
Set<Long> ids = associatedModelMap.get(associatedTableName);
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
mDatabase.update(associatedTableName, values, getWhereOfIdsWithOr(ids), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the associations breaks between current model and associated models,
|
||||
* clear all the associated models' foreign key value if it exists.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
*/
|
||||
private void clearFKValueInAssociatedTable(DataSupport baseObj) {
|
||||
List<String> associatedTableNames = baseObj.getListToClearAssociatedFK();
|
||||
for (String associatedTableName : associatedTableNames) {
|
||||
String fkColumnName = getForeignKeyColumnName(baseObj.getTableName());
|
||||
ContentValues values = new ContentValues();
|
||||
values.putNull(fkColumnName);
|
||||
String whereClause = fkColumnName + " = " + baseObj.getBaseObjId();
|
||||
mDatabase.update(associatedTableName, values, whereClause, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert values into intermediate join tables for self model and associated
|
||||
* models.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
* @param isUpdate
|
||||
* The current action is update or not.
|
||||
*/
|
||||
private void insertIntermediateJoinTableValue(DataSupport baseObj, boolean isUpdate) {
|
||||
Map<String, Set<Long>> associatedIdsM2M = baseObj.getAssociatedModelsMapForJoinTable();
|
||||
ContentValues values = new ContentValues();
|
||||
for (String associatedTableName : associatedIdsM2M.keySet()) {
|
||||
String joinTableName = getIntermediateTableName(baseObj, associatedTableName);
|
||||
if (isUpdate) {
|
||||
mDatabase.delete(joinTableName, getWhereForJoinTableToDelete(baseObj),
|
||||
new String[] { String.valueOf(baseObj.getBaseObjId()) });
|
||||
}
|
||||
Set<Long> associatedIdsM2MSet = associatedIdsM2M.get(associatedTableName);
|
||||
for (long associatedId : associatedIdsM2MSet) {
|
||||
values.clear();
|
||||
values.put(getForeignKeyColumnName(baseObj.getTableName()), baseObj.getBaseObjId());
|
||||
values.put(getForeignKeyColumnName(associatedTableName), associatedId);
|
||||
mDatabase.insert(joinTableName, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the where clause to delete intermediate join table's value for
|
||||
* updating.
|
||||
*
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
* @return The where clause to execute.
|
||||
*/
|
||||
private String getWhereForJoinTableToDelete(DataSupport baseObj) {
|
||||
StringBuilder where = new StringBuilder();
|
||||
where.append(getForeignKeyColumnName(baseObj.getTableName()));
|
||||
where.append(" = ?");
|
||||
return where.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge should assign id value to model's id field. The principle is that
|
||||
* if id name is not null, id type is not null and id is greater than 0,
|
||||
* then should assign id value to it.
|
||||
*
|
||||
* @param idName
|
||||
* The name of id field.
|
||||
* @param idType
|
||||
* The type of id field.
|
||||
* @param id
|
||||
* The value of id.
|
||||
* @return If id name is not null, id type is not null and id is greater
|
||||
* than 0, return true. Otherwise return false.
|
||||
*/
|
||||
private boolean shouldGiveModelIdValue(String idName, Class<?> idType, long id) {
|
||||
return idName != null && idType != null && id > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the generic data in generic tables. Need to delete the related generic data before
|
||||
* saving, because generic data has no id.
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
*@param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @param id
|
||||
* The id of current model.
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
*/
|
||||
private void updateGenericTables(DataSupport baseObj, List<Field> supportedGenericFields,
|
||||
long id) throws IllegalAccessException, InvocationTargetException {
|
||||
for (Field field : supportedGenericFields) {
|
||||
field.setAccessible(true);
|
||||
Collection<?> collection = (Collection<?>) field.get(baseObj);
|
||||
if (collection != null) {
|
||||
String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName());
|
||||
String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName());
|
||||
mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)});
|
||||
for (Object object : collection) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(genericValueIdColumnName, id);
|
||||
Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), object };
|
||||
Class<?>[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) };
|
||||
DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes);
|
||||
mDatabase.insert(tableName, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
|
||||
import org.litepal.crud.model.AssociationsInfo;
|
||||
import org.litepal.exceptions.DataSupportException;
|
||||
import org.litepal.util.BaseUtility;
|
||||
import org.litepal.util.DBUtility;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.litepal.util.BaseUtility.changeCase;
|
||||
|
||||
/**
|
||||
* This is a component under DataSupport. It deals with the updating stuff as
|
||||
* primary task. Either updating specifying data with id or updating multiple
|
||||
* lines with conditions can be done here.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 1.1
|
||||
*/
|
||||
class UpdateHandler extends DataHandler {
|
||||
|
||||
/**
|
||||
* Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not
|
||||
* allow to create instance of UpdateHandler out of CRUD package.
|
||||
*
|
||||
* @param db
|
||||
* The instance of SQLiteDatabase.
|
||||
*/
|
||||
UpdateHandler(SQLiteDatabase db) {
|
||||
mDatabase = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to update. Using
|
||||
* baseObj to decide which table to update, and id to decide a specific row.
|
||||
* The value that need to update is stored in baseObj.
|
||||
*
|
||||
* @param baseObj
|
||||
* Which table to update by model instance.
|
||||
* @param id
|
||||
* Which record to update.
|
||||
* @return The number of rows affected.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
int onUpdate(DataSupport baseObj, long id) throws SecurityException, IllegalArgumentException,
|
||||
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
List<Field> supportedFields = getSupportedFields(baseObj.getClassName());
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(baseObj.getClassName());
|
||||
updateGenericTables(baseObj, supportedGenericFields, id);
|
||||
ContentValues values = new ContentValues();
|
||||
putFieldsValue(baseObj, supportedFields, values);
|
||||
putFieldsToDefaultValue(baseObj, values, id);
|
||||
if (values.size() > 0) {
|
||||
return mDatabase.update(baseObj.getTableName(), values, "id = " + id, null);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to update. Using
|
||||
* modelClass to decide which table to update, and id to decide a specific
|
||||
* row. The value that need to update is stored in ContentValues.
|
||||
*
|
||||
* @param modelClass
|
||||
* Which table to update by class.
|
||||
* @param id
|
||||
* Which record to update.
|
||||
* @param values
|
||||
* A map from column names to new column values. null is a valid
|
||||
* value that will be translated to NULL.
|
||||
* @return The number of rows affected.
|
||||
*/
|
||||
int onUpdate(Class<?> modelClass, long id, ContentValues values) {
|
||||
if (values.size() > 0) {
|
||||
convertContentValues(values);
|
||||
return mDatabase.update(getTableName(modelClass), values, "id = " + id, null);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to update multiple
|
||||
* rows. Using baseObj to decide which table to update, and conditions
|
||||
* representing the WHERE part of an SQL statement. The value that need to
|
||||
* update is stored in baseObj.
|
||||
*
|
||||
* @param baseObj
|
||||
* Which table to update by model instance.
|
||||
* @param conditions
|
||||
* A string array representing the WHERE part of an SQL
|
||||
* statement.
|
||||
* @return The number of rows affected.
|
||||
* @throws java.lang.reflect.InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SecurityException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
int onUpdateAll(DataSupport baseObj, String... conditions) throws SecurityException,
|
||||
IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
List<Field> supportedFields = getSupportedFields(baseObj.getClassName());
|
||||
List<Field> supportedGenericFields = getSupportedGenericFields(baseObj.getClassName());
|
||||
long[] ids = null;
|
||||
if (!supportedGenericFields.isEmpty()) {
|
||||
List<DataSupport> list = (List<DataSupport>) DataSupport.select("id").where(conditions).find(baseObj.getClass());
|
||||
if (list.size() > 0) {
|
||||
ids = new long[list.size()];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
DataSupport dataSupport = list.get(i);
|
||||
ids[i] = dataSupport.getBaseObjId();
|
||||
}
|
||||
updateGenericTables(baseObj, supportedGenericFields, ids);
|
||||
}
|
||||
}
|
||||
ContentValues values = new ContentValues();
|
||||
putFieldsValue(baseObj, supportedFields, values);
|
||||
putFieldsToDefaultValue(baseObj, values, ids);
|
||||
return doUpdateAllAction(baseObj.getTableName(), values, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open interface for other classes in CRUD package to update multiple
|
||||
* rows. Using tableName to decide which table to update, and conditions
|
||||
* representing the WHERE part of an SQL statement. The value that need to
|
||||
* update is stored in ContentValues.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to update.
|
||||
* @param conditions
|
||||
* A string array representing the WHERE part of an SQL
|
||||
* statement.
|
||||
* @param values
|
||||
* A map from column names to new column values. null is a valid
|
||||
* value that will be translated to NULL.
|
||||
* @return The number of rows affected.
|
||||
*/
|
||||
int onUpdateAll(String tableName, ContentValues values, String... conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (conditions != null && conditions.length > 0) {
|
||||
conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]);
|
||||
}
|
||||
convertContentValues(values);
|
||||
return doUpdateAllAction(tableName, values, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the action for updating multiple rows. It will check the validity of
|
||||
* conditions, then update rows in database. If the format of conditions is
|
||||
* invalid, throw DataSupportException.
|
||||
*
|
||||
* @param tableName
|
||||
* Which table to delete from.
|
||||
* @param conditions
|
||||
* A string array representing the WHERE part of an SQL
|
||||
* statement.
|
||||
* @param values
|
||||
* A map from column names to new column values. null is a valid
|
||||
* value that will be translated to NULL.
|
||||
* @return The number of rows affected.
|
||||
*/
|
||||
private int doUpdateAllAction(String tableName, ContentValues values, String... conditions) {
|
||||
BaseUtility.checkConditionsCorrect(conditions);
|
||||
if (values.size() > 0) {
|
||||
return mDatabase.update(tableName, values, getWhereClause(conditions),
|
||||
getWhereArgs(conditions));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate all the fields that need to set to default value. If the field is
|
||||
* id, ignore it. Or put the default value of field into ContentValues.
|
||||
*
|
||||
* @param baseObj
|
||||
* Which table to update by model instance.
|
||||
* @param values
|
||||
* To store data of current model for persisting or updating.
|
||||
* @param ids
|
||||
* The id array of query result.
|
||||
*/
|
||||
private void putFieldsToDefaultValue(DataSupport baseObj, ContentValues values, long... ids) {
|
||||
String fieldName = null;
|
||||
try {
|
||||
DataSupport emptyModel = getEmptyModel(baseObj);
|
||||
Class<?> emptyModelClass = emptyModel.getClass();
|
||||
for (String name : baseObj.getFieldsToSetToDefault()) {
|
||||
if (!isIdColumn(name)) {
|
||||
fieldName = name;
|
||||
Field field = emptyModelClass.getDeclaredField(fieldName);
|
||||
if (isCollection(field.getType())) {
|
||||
if (ids != null && ids.length > 0) {
|
||||
String genericTypeName = getGenericTypeName(field);
|
||||
if (BaseUtility.isGenericTypeSupported(genericTypeName)) {
|
||||
String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName());
|
||||
String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName());
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
boolean needOr = false;
|
||||
for (long id : ids) {
|
||||
if (needOr) {
|
||||
whereClause.append(" or ");
|
||||
}
|
||||
whereClause.append(genericValueIdColumnName).append(" = ").append(id);
|
||||
needOr = true;
|
||||
}
|
||||
mDatabase.delete(tableName, whereClause.toString(), null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
putContentValuesForUpdate(emptyModel, field, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new DataSupportException(DataSupportException.noSuchFieldExceptioin(
|
||||
baseObj.getClassName(), fieldName), e);
|
||||
} catch (Exception e) {
|
||||
throw new DataSupportException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused currently.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int doUpdateAssociations(DataSupport baseObj, long id, ContentValues values) {
|
||||
int rowsAffected = 0;
|
||||
analyzeAssociations(baseObj);
|
||||
updateSelfTableForeignKey(baseObj, values);
|
||||
rowsAffected += updateAssociatedTableForeignKey(baseObj, id);
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the associations of baseObj and store the result in it. The
|
||||
* associations will be used when deleting referenced data of baseObj.
|
||||
* Unused currently.
|
||||
*
|
||||
* @param baseObj
|
||||
* The record to update.
|
||||
*/
|
||||
private void analyzeAssociations(DataSupport baseObj) {
|
||||
try {
|
||||
Collection<AssociationsInfo> associationInfos = getAssociationInfo(baseObj
|
||||
.getClassName());
|
||||
analyzeAssociatedModels(baseObj, associationInfos);
|
||||
} catch (Exception e) {
|
||||
throw new DataSupportException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused currently.
|
||||
*/
|
||||
private void updateSelfTableForeignKey(DataSupport baseObj, ContentValues values) {
|
||||
Map<String, Long> associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK();
|
||||
for (String associatedTable : associatedModelMap.keySet()) {
|
||||
String fkName = getForeignKeyColumnName(associatedTable);
|
||||
values.put(fkName, associatedModelMap.get(associatedTable));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused currently.
|
||||
*/
|
||||
private int updateAssociatedTableForeignKey(DataSupport baseObj, long id) {
|
||||
Map<String, Set<Long>> associatedModelMap = baseObj.getAssociatedModelsMapWithFK();
|
||||
ContentValues values = new ContentValues();
|
||||
for (String associatedTable : associatedModelMap.keySet()) {
|
||||
values.clear();
|
||||
String fkName = getForeignKeyColumnName(baseObj.getTableName());
|
||||
values.put(fkName, id);
|
||||
Set<Long> ids = associatedModelMap.get(associatedTable);
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
return mDatabase.update(associatedTable, values, getWhereOfIdsWithOr(ids), null);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the generic data in generic tables. Need to delete the related generic data before
|
||||
* saving, because generic data has no id. If generic collection is null or empty, the operation
|
||||
* will be abort. Clear generic collection data while updating should use {@link DataSupport#setToDefault(String)}
|
||||
* method.
|
||||
* @param baseObj
|
||||
* Current model that is persisted.
|
||||
*@param supportedGenericFields
|
||||
* List of all supported generic fields.
|
||||
* @param ids
|
||||
* The id array of models.
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
*/
|
||||
private void updateGenericTables(DataSupport baseObj, List<Field> supportedGenericFields,
|
||||
long... ids) throws IllegalAccessException, InvocationTargetException {
|
||||
if (ids != null && ids.length > 0) {
|
||||
for (Field field : supportedGenericFields) {
|
||||
field.setAccessible(true);
|
||||
Collection<?> collection = (Collection<?>) field.get(baseObj);
|
||||
if (collection != null && !collection.isEmpty()) {
|
||||
String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName());
|
||||
String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName());
|
||||
for (long id : ids) {
|
||||
mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)});
|
||||
for (Object object : collection) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(genericValueIdColumnName, id);
|
||||
Object[] parameters = new Object[] { DBUtility.convertToValidColumnName(changeCase(field.getName())), object };
|
||||
Class<?>[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) };
|
||||
DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes);
|
||||
mDatabase.insert(tableName, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The keys in ContentValues may be put as valid in Java but invalid in database. So convert
|
||||
* them into valid keys.
|
||||
* @param values
|
||||
* A map from column names to new column values. null is a valid
|
||||
* value that will be translated to NULL.
|
||||
*/
|
||||
private void convertContentValues(ContentValues values) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
Map<String, Object> valuesToConvert = new HashMap<String, Object>();
|
||||
for (String key : values.keySet()) {
|
||||
if (DBUtility.isFieldNameConflictWithSQLiteKeywords(key)) {
|
||||
Object object = values.get(key);
|
||||
valuesToConvert.put(key, object);
|
||||
}
|
||||
}
|
||||
for (String key : valuesToConvert.keySet()) {
|
||||
String convertedKey = DBUtility.convertToValidColumnName(key);
|
||||
Object object = values.get(key);
|
||||
values.remove(key);
|
||||
if (object == null) {
|
||||
values.putNull(convertedKey);
|
||||
} else {
|
||||
String className = object.getClass().getName();
|
||||
if ("java.lang.Byte".equals(className)) {
|
||||
values.put(convertedKey, (Byte) object);
|
||||
} else if ("[B".equals(className)) {
|
||||
values.put(convertedKey, (byte[]) object);
|
||||
} else if ("java.lang.Boolean".equals(className)) {
|
||||
values.put(convertedKey, (Boolean) object);
|
||||
} else if ("java.lang.String".equals(className)) {
|
||||
values.put(convertedKey, (String) object);
|
||||
} else if ("java.lang.Float".equals(className)) {
|
||||
values.put(convertedKey, (Float) object);
|
||||
} else if ("java.lang.Long".equals(className)) {
|
||||
values.put(convertedKey, (Long) object);
|
||||
} else if ("java.lang.Integer".equals(className)) {
|
||||
values.put(convertedKey, (Integer) object);
|
||||
} else if ("java.lang.Short".equals(className)) {
|
||||
values.put(convertedKey, (Short) object);
|
||||
} else if ("java.lang.Double".equals(className)) {
|
||||
values.put(convertedKey, (Double) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud.async;
|
||||
|
||||
/**
|
||||
* A simple async executor to run tasks in background thread.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 2017/2/22
|
||||
*/
|
||||
public abstract class AsyncExecutor {
|
||||
|
||||
/**
|
||||
* Task that pending to run.
|
||||
*/
|
||||
private Runnable pendingTask;
|
||||
|
||||
/**
|
||||
* Submit a task for pending executing.
|
||||
* @param task
|
||||
* The task with specific database operation.
|
||||
*/
|
||||
public void submit(Runnable task) {
|
||||
pendingTask = task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the pending task in background thread.
|
||||
*/
|
||||
void execute() {
|
||||
if (pendingTask != null) {
|
||||
new Thread(pendingTask).start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud.async;
|
||||
|
||||
import org.litepal.crud.callback.AverageCallback;
|
||||
|
||||
/**
|
||||
* Executor for average query in background.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 2017/2/22
|
||||
*/
|
||||
public class AverageExecutor extends AsyncExecutor {
|
||||
|
||||
private AverageCallback cb;
|
||||
|
||||
/**
|
||||
* Register a callback listener and async task will start executing right away.
|
||||
* @param callback
|
||||
* Callback for average query in background.
|
||||
*/
|
||||
public void listen(AverageCallback callback) {
|
||||
cb = callback;
|
||||
execute();
|
||||
}
|
||||
|
||||
public AverageCallback getListener() {
|
||||
return cb;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud.async;
|
||||
|
||||
import org.litepal.crud.callback.CountCallback;
|
||||
|
||||
/**
|
||||
* Executor for count query in background.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 2017/2/22
|
||||
*/
|
||||
public class CountExecutor extends AsyncExecutor {
|
||||
|
||||
private CountCallback cb;
|
||||
|
||||
/**
|
||||
* Register a callback listener and async task will start executing right away.
|
||||
* @param callback
|
||||
* Callback for count query in background.
|
||||
*/
|
||||
public void listen(CountCallback callback) {
|
||||
cb = callback;
|
||||
execute();
|
||||
}
|
||||
|
||||
public CountCallback getListener() {
|
||||
return cb;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud.async;
|
||||
|
||||
import org.litepal.crud.callback.FindCallback;
|
||||
|
||||
/**
|
||||
* Executor for find record in background.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 2017/2/22
|
||||
*/
|
||||
public class FindExecutor extends AsyncExecutor {
|
||||
|
||||
private FindCallback cb;
|
||||
|
||||
/**
|
||||
* Register a callback listener and async task will start executing right away.
|
||||
* @param callback
|
||||
* Callback for find record in background.
|
||||
*/
|
||||
public void listen(FindCallback callback) {
|
||||
cb = callback;
|
||||
execute();
|
||||
}
|
||||
|
||||
public FindCallback getListener() {
|
||||
return cb;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Tony Green, LitePal Framework Open Source Project
|
||||
*
|
||||
* Licensed 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.litepal.crud.async;
|
||||
|
||||
import org.litepal.crud.callback.FindMultiCallback;
|
||||
|
||||
/**
|
||||
* Executor for find multiple records in background.
|
||||
*
|
||||
* @author Tony Green
|
||||
* @since 2017/2/22
|
||||
*/
|
||||
public class FindMultiExecutor extends AsyncExecutor {
|
||||
|
||||
private FindMultiCallback cb;
|
||||
|
||||
/**
|
||||
* Register a callback listener and async task will start executing right away.
|
||||
* @param callback
|
||||
* Callback for find multiple records in background.
|
||||
*/
|
||||
public void listen(FindMultiCallback callback) {
|
||||
cb = callback;
|
||||
execute();
|
||||
}
|
||||
|
||||
public FindMultiCallback getListener() {
|
||||
return cb;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue