/*
* Copyright 2011 Future Systems
*
* 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.krakenapps.logdb.query.command;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.krakenapps.logdb.LogQueryCommand;
import org.krakenapps.logdb.query.FileBufferMap;
import org.krakenapps.logdb.query.command.Function.Sum;
public class Timechart extends LogQueryCommand {
public static enum Span {
Second(Calendar.SECOND, 1000L), Minute(Calendar.MINUTE, 60 * 1000L), Hour(Calendar.HOUR_OF_DAY, 60 * 60 * 1000L), Day(
Calendar.DAY_OF_MONTH, 24 * 60 * 60 * 1000L), Week(Calendar.WEEK_OF_YEAR, 7 * 24 * 60 * 60 * 1000L), Month(Calendar.MONTH,
0L), Year(Calendar.YEAR, 0L);
private int calendarField;
private long millis;
private Span(int calendarField, long millis) {
this.calendarField = calendarField;
this.millis = millis;
}
}
private FileBufferMap<Date, Object[]> data;
private Map<Date, Long> amount;
private Span spanField;
private int spanAmount;
private Function[] values;
private String keyField;
public Timechart(Function[] values, String keyField) {
this(Span.Day, 1, values, keyField);
}
public Timechart(Span spanField, int spanAmount, Function[] values, String keyField) {
this.spanField = spanField;
this.spanAmount = spanAmount;
this.values = values;
this.keyField = keyField;
}
@Override
public void init() {
super.init();
try {
this.data = new FileBufferMap<Date, Object[]>(FunctionCodec.instance);
} catch (IOException e) {
}
this.amount = new HashMap<Date, Long>();
}
@SuppressWarnings("unchecked")
@Override
public void push(LogMap m) {
Date row = getKey((Date) m.get("_time"));
if (!data.containsKey(row)) {
Object[] v = new Object[values.length];
for (int i = 0; i < values.length; i++)
v[i] = new HashMap<String, Function>();
data.put(row, v);
Calendar c = Calendar.getInstance();
c.setTime(row);
c.add(spanField.calendarField, spanAmount);
amount.put(row, c.getTimeInMillis() - row.getTime());
}
String key = null;
if (keyField != null) {
if (m.get(keyField) == null)
return;
key = m.get(keyField).toString();
}
Object[] blocks = data.get(row);
for (int i = 0; i < blocks.length; i++) {
Map<String, Function> block = (Map<String, Function>) blocks[i];
String k = (key != null) ? key : values[i].toString();
if (!block.containsKey(k)) {
Function f = values[i].clone();
if (f instanceof PerTime)
((PerTime) f).amount = amount.get(row);
block.put(k, f);
}
Function func = block.get(k);
m.get(func.getTarget());
func.put(m);
}
}
@Override
public boolean isReducer() {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void eof() {
this.status = Status.Finalizing;
List<Date> sortedKey = new ArrayList<Date>(data.keySet());
Collections.sort(sortedKey);
for (Date key : sortedKey) {
Object[] values = data.get(key);
Map<String, Object> m = new HashMap<String, Object>();
m.put("_time", key);
if (values.length == 1) {
for (String k : ((Map<String, Function>) values[0]).keySet()) {
Function v = ((Map<String, Function>) values[0]).get(k);
m.put(k, v.getResult());
}
} else {
for (Object value : values) {
for (String k : ((Map<String, Function>) value).keySet()) {
Function v = ((Map<String, Function>) value).get(k);
if (keyField != null)
m.put(v.toString() + ":" + k, v.getResult());
else
m.put(k, v.getResult());
}
}
}
write(new LogMap(m));
}
data.close();
data = null;
amount = null;
super.eof();
}
private Date getKey(Date date) {
long time = date.getTime();
if (spanField == Span.Second || spanField == Span.Minute || spanField == Span.Hour || spanField == Span.Day
|| spanField == Span.Week) {
time += 291600000L; // base to Monday, 00:00:00
time -= time % (spanField.millis * spanAmount);
time -= 291600000L;
} else {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(time - time % Span.Second.millis);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.DAY_OF_MONTH, 1);
if (spanField == Span.Month) {
int monthOffset = c.get(Calendar.YEAR) * 12;
int month = monthOffset + c.get(Calendar.MONTH);
month -= month % spanAmount;
month -= monthOffset;
c.add(Calendar.MONTH, month);
time = c.getTimeInMillis();
} else if (spanField == Span.Year) {
int year = c.get(Calendar.YEAR);
c.set(Calendar.YEAR, year - (year % spanAmount));
time = c.getTimeInMillis();
}
}
return new Date(time);
}
public static final Map<String, Class<? extends Function>> func;
static {
func = new HashMap<String, Class<? extends Function>>();
func.put("per_second", PerSecond.class);
func.put("per_minute", PerMinute.class);
func.put("per_hour", PerHour.class);
func.put("per_day", PerDay.class);
}
public static abstract class PerTime extends Sum {
private long amount;
abstract protected long getTimeLength();
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
@Override
public Object getResult() {
if (super.getResult() == null)
return null;
return NumberUtil.div(super.getResult(), (double) amount / (double) getTimeLength());
}
}
public static class PerSecond extends PerTime {
@Override
protected long getTimeLength() {
return 1000L;
}
}
public static class PerMinute extends PerTime {
@Override
protected long getTimeLength() {
return 60 * 1000L;
}
}
public static class PerHour extends PerTime {
@Override
protected long getTimeLength() {
return 60 * 60 * 1000L;
}
}
public static class PerDay extends PerTime {
@Override
protected long getTimeLength() {
return 24 * 60 * 60 * 1000L;
}
}
}